Upgrade dependencies
This commit is contained in:
parent
3ee4876290
commit
32ccfc24ee
53
go.mod
53
go.mod
@ -1,24 +1,24 @@
|
||||
module github.com/datarhei/core/v16
|
||||
|
||||
go 1.21
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.22.1
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.45
|
||||
github.com/99designs/gqlgen v0.17.47
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/adhocore/gronx v1.8.1
|
||||
github.com/andybalholm/brotli v1.1.0
|
||||
github.com/atrox/haikunatorgo/v2 v2.0.1
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/casbin/casbin/v2 v2.88.0
|
||||
github.com/caddyserver/certmagic v0.21.2
|
||||
github.com/casbin/casbin/v2 v2.89.0
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894
|
||||
github.com/datarhei/gosrt v0.6.0
|
||||
github.com/datarhei/joy4 v0.0.0-20240229100136-43bcaf8ef5e7
|
||||
github.com/datarhei/joy4 v0.0.0-20240528121836-da80d79b6435
|
||||
github.com/fujiwara/shapeio v1.0.0
|
||||
github.com/go-playground/validator/v10 v10.19.0
|
||||
github.com/go-playground/validator/v10 v10.20.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/gops v0.3.28
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -35,18 +35,18 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/minio/minio-go/v7 v7.0.70
|
||||
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.3
|
||||
github.com/shirou/gopsutil/v3 v3.24.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/echo-swagger v1.4.1
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/vektah/gqlparser/v2 v2.5.11
|
||||
github.com/vektah/gqlparser/v2 v2.5.12
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.etcd.io/bbolt v1.3.9
|
||||
go.etcd.io/bbolt v1.3.10
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/mod v0.17.0
|
||||
)
|
||||
|
||||
@ -59,13 +59,14 @@ require (
|
||||
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/casbin/govaluate v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
@ -86,10 +87,10 @@ require (
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/mholt/acmez/v2 v2.0.1 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
@ -98,30 +99,30 @@ require (
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.14.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sosodev/duration v1.3.0 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
108
go.sum
108
go.sum
@ -1,12 +1,12 @@
|
||||
github.com/99designs/gqlgen v0.17.45 h1:bH0AH67vIJo8JKNKPJP+pOPpQhZeuVRQLf53dKIpDik=
|
||||
github.com/99designs/gqlgen v0.17.45/go.mod h1:Bas0XQ+Jiu/Xm5E33jC8sES3G+iC2esHBMXcq0fUPs0=
|
||||
github.com/99designs/gqlgen v0.17.47 h1:M9DTK8X3+3ATNBfZlHBwMwNngn4hhZWDxNmTiuQU5tQ=
|
||||
github.com/99designs/gqlgen v0.17.47/go.mod h1:ejVkldSdtmuudqmtfaiqjwlGXWAhIv0DKXGXFY25F04=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
|
||||
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
|
||||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/adhocore/gronx v1.8.1 h1:F2mLTG5sB11z7vplwD4iydz3YCEjstSfYmCrdSm3t6A=
|
||||
github.com/adhocore/gronx v1.8.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
|
||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||
@ -35,10 +35,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/casbin/casbin/v2 v2.88.0 h1:JFHId/aIFvNvPnTwUP+tTtVAjSh3eidslFzy+5LpSeU=
|
||||
github.com/casbin/casbin/v2 v2.88.0/go.mod h1:jX8uoN4veP85O/n2674r2qtfSXI6myvxW85f6TH50fw=
|
||||
github.com/caddyserver/certmagic v0.21.2 h1:O18LtaYBGDooyy257cYePnhp4lPfz6TaJELil6Q1fDg=
|
||||
github.com/caddyserver/certmagic v0.21.2/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/casbin/casbin/v2 v2.89.0 h1:XpgheobgazzxruVClvyNRMyAn+l1g9O4LY6XAgtaDkg=
|
||||
github.com/casbin/casbin/v2 v2.89.0/go.mod h1:jX8uoN4veP85O/n2674r2qtfSXI6myvxW85f6TH50fw=
|
||||
github.com/casbin/govaluate v1.1.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
github.com/casbin/govaluate v1.1.1 h1:J1rFKIBhiC5xr0APd5HP6rDL+xt+BRoyq1pa4o2i/5c=
|
||||
github.com/casbin/govaluate v1.1.1/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
@ -47,16 +49,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240424105158-86a7f261b92c h1:RIzMclqmSYwpMZxyW7nLg0XyKjY6prQWcuIdgm97U8o=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240424105158-86a7f261b92c/go.mod h1:7XrUOUlB165Gs8JUE4lzVuNve6HW90Yz3/+lTY2EV4A=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894 h1:ZQCTobOGpzfuZxgMWsZviFSXfI5QuttkTgPQz1PKbhU=
|
||||
github.com/datarhei/core-client-go/v16 v16.11.1-0.20240429143858-23ad5985b894/go.mod h1:Mu2bHqvGJe46KvAhY2ElohuQYhHB64PZeaCNDv6C5b8=
|
||||
github.com/datarhei/gosrt v0.6.0 h1:HrrXAw90V78ok4WMIhX6se1aTHPCn82Sg2hj+PhdmGc=
|
||||
github.com/datarhei/gosrt v0.6.0/go.mod h1:fsOWdLSHUHShHjgi/46h6wjtdQrtnSdAQFnlas8ONxs=
|
||||
github.com/datarhei/joy4 v0.0.0-20240229100136-43bcaf8ef5e7 h1:MG5XQMTTDPcuvvRzc1c37QbwgDbYPhKmPFo9gSaPdBE=
|
||||
github.com/datarhei/joy4 v0.0.0-20240229100136-43bcaf8ef5e7/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw=
|
||||
github.com/datarhei/joy4 v0.0.0-20240528121836-da80d79b6435 h1:bXcqdyQWtKyb1i82qLMqi4DxbVrZMpk0oVmKtWJjWhg=
|
||||
github.com/datarhei/joy4 v0.0.0-20240528121836-da80d79b6435/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -66,12 +66,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M=
|
||||
github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@ -95,13 +95,13 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
@ -194,8 +194,8 @@ github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfs
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk=
|
||||
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@ -208,8 +208,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
@ -242,8 +242,8 @@ github.com/prep/average v0.0.0-20200506183628-d26c465f48c3/go.mod h1:0ZE5gcyWKS1
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -256,8 +256,8 @@ github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
|
||||
github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ=
|
||||
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
|
||||
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
@ -268,16 +268,16 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
|
||||
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sosodev/duration v1.3.0 h1:g3E6mto+hFdA2uZXeNDYff8LYeg7v5D4YKP/Ng/NUkE=
|
||||
github.com/sosodev/duration v1.3.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@ -306,14 +306,14 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8=
|
||||
github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
|
||||
github.com/vektah/gqlparser/v2 v2.5.12 h1:COMhVVnql6RoaF7+aTBWiTADdpLGyZWU3K/NwW0ph98=
|
||||
github.com/vektah/gqlparser/v2 v2.5.12/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@ -321,8 +321,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
@ -331,8 +331,8 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@ -343,15 +343,15 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -376,21 +376,21 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@ -82,13 +82,15 @@ func New(config Config) (Manager, error) {
|
||||
func (am *access) HasPolicy(name, domain string, types []string, resource string, actions []string) bool {
|
||||
policy := []string{name, domain, EncodeResource(types, resource), EncodeActions(actions)}
|
||||
|
||||
return am.enforcer.HasPolicy(policy)
|
||||
hasPolicy, _ := am.enforcer.HasPolicy(policy)
|
||||
|
||||
return hasPolicy
|
||||
}
|
||||
|
||||
func (am *access) AddPolicy(name, domain string, types []string, resource string, actions []string) error {
|
||||
policy := []string{name, domain, EncodeResource(types, resource), EncodeActions(actions)}
|
||||
|
||||
if am.enforcer.HasPolicy(policy) {
|
||||
if hasPolicy, _ := am.enforcer.HasPolicy(policy); hasPolicy {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -98,8 +100,12 @@ func (am *access) AddPolicy(name, domain string, types []string, resource string
|
||||
}
|
||||
|
||||
func (am *access) RemovePolicy(name, domain string, types []string, resource string, actions []string) error {
|
||||
policies := am.enforcer.GetFilteredPolicy(0, name, domain, EncodeResource(types, resource), EncodeActions(actions))
|
||||
_, err := am.enforcer.RemovePolicies(policies)
|
||||
policies, err := am.enforcer.GetFilteredPolicy(0, name, domain, EncodeResource(types, resource), EncodeActions(actions))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = am.enforcer.RemovePolicies(policies)
|
||||
|
||||
return err
|
||||
}
|
||||
@ -107,7 +113,10 @@ func (am *access) RemovePolicy(name, domain string, types []string, resource str
|
||||
func (am *access) ListPolicies(name, domain string, types []string, resource string, actions []string) []Policy {
|
||||
policies := []Policy{}
|
||||
|
||||
ps := am.enforcer.GetFilteredPolicy(0, name, domain, EncodeResource(types, resource), EncodeActions(actions))
|
||||
ps, err := am.enforcer.GetFilteredPolicy(0, name, domain, EncodeResource(types, resource), EncodeActions(actions))
|
||||
if err != nil {
|
||||
return policies
|
||||
}
|
||||
|
||||
for _, p := range ps {
|
||||
types, resource := DecodeResource(p[2])
|
||||
|
||||
5
vendor/github.com/99designs/gqlgen/.gitignore
generated
vendored
5
vendor/github.com/99designs/gqlgen/.gitignore
generated
vendored
@ -18,3 +18,8 @@ gqlgen
|
||||
*.exe
|
||||
|
||||
node_modules
|
||||
|
||||
# generated files
|
||||
/api/testdata/default/graph/generated.go
|
||||
/api/testdata/federation2/graph/federation.go
|
||||
/api/testdata/federation2/graph/generated.go
|
||||
25
vendor/github.com/99designs/gqlgen/.golangci.yml
generated
vendored
25
vendor/github.com/99designs/gqlgen/.golangci.yml
generated
vendored
@ -1,11 +1,25 @@
|
||||
run:
|
||||
tests: true
|
||||
skip-dirs:
|
||||
- bin
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,[rR]ead|[wW]rite|[cC]lose,io:Copy
|
||||
exclude-functions:
|
||||
- (io.Writer).Write
|
||||
- io.Copy
|
||||
- io.WriteString
|
||||
revive:
|
||||
enable-all-rules: false
|
||||
rules:
|
||||
- name: empty-lines
|
||||
testifylint:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bool-compare
|
||||
- compares
|
||||
- error-is-as
|
||||
- error-nil
|
||||
- expected-actual
|
||||
- nil-compare
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
@ -22,14 +36,19 @@ linters:
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- bin
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- errcheck
|
||||
|
||||
1143
vendor/github.com/99designs/gqlgen/CHANGELOG.md
generated
vendored
1143
vendor/github.com/99designs/gqlgen/CHANGELOG.md
generated
vendored
File diff suppressed because it is too large
Load Diff
5
vendor/github.com/99designs/gqlgen/README.md
generated
vendored
5
vendor/github.com/99designs/gqlgen/README.md
generated
vendored
@ -20,9 +20,9 @@ Still not convinced enough to use **gqlgen**? Compare **gqlgen** with other Go g
|
||||
cd example
|
||||
go mod init example
|
||||
|
||||
2. Add `github.com/99designs/gqlgen` to your [project's tools.go](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module)
|
||||
2. Add `github.com/99designs/gqlgen` to your [project's tools.go](https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module)
|
||||
|
||||
printf '// +build tools\npackage tools\nimport (_ "github.com/99designs/gqlgen"\n _ "github.com/99designs/gqlgen/graphql/introspection")' | gofmt > tools.go
|
||||
printf '//go:build tools\npackage tools\nimport (_ "github.com/99designs/gqlgen"\n _ "github.com/99designs/gqlgen/graphql/introspection")' | gofmt > tools.go
|
||||
|
||||
go mod tidy
|
||||
|
||||
@ -135,6 +135,7 @@ models:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.IntID # a go integer
|
||||
- github.com/99designs/gqlgen/graphql.ID # or a go string
|
||||
- github.com/99designs/gqlgen/graphql.UintID # or a go uint
|
||||
```
|
||||
|
||||
This means gqlgen will be able to automatically bind to strings or ints for models you have written yourself, but the
|
||||
|
||||
3
vendor/github.com/99designs/gqlgen/RELEASE-CHECKLIST.md
generated
vendored
3
vendor/github.com/99designs/gqlgen/RELEASE-CHECKLIST.md
generated
vendored
@ -6,10 +6,9 @@ Assuming the next version is $NEW_VERSION=v0.16.0 or something like that.
|
||||
./bin/release $NEW_VERSION
|
||||
```
|
||||
2. git-chglog -o CHANGELOG.md
|
||||
3. go generate ./...; cd _examples; go generate ./...; cd ..
|
||||
3. go generate ./...
|
||||
4. git commit and push the CHANGELOG.md
|
||||
5. Go to https://github.com/99designs/gqlgen/releases and draft new release, autogenerate the release notes, and Create a discussion for this release
|
||||
6. Comment on the release discussion with any really important notes (breaking changes)
|
||||
|
||||
I used https://github.com/git-chglog/git-chglog to automate the changelog maintenance process for now. We could just as easily use go releaser to make the whole thing automated.
|
||||
|
||||
|
||||
7
vendor/github.com/99designs/gqlgen/api/generate.go
generated
vendored
7
vendor/github.com/99designs/gqlgen/api/generate.go
generated
vendored
@ -13,6 +13,11 @@ import (
|
||||
"github.com/99designs/gqlgen/plugin/resolvergen"
|
||||
)
|
||||
|
||||
var (
|
||||
urlRegex = regexp.MustCompile(`(?s)@link.*\(.*url:.*?"(.*?)"[^)]+\)`) // regex to grab the url of a link directive, should it exist
|
||||
versionRegex = regexp.MustCompile(`v(\d+).(\d+)$`) // regex to grab the version number from a url
|
||||
)
|
||||
|
||||
func Generate(cfg *config.Config, option ...Option) error {
|
||||
_ = syscall.Unlink(cfg.Exec.Filename)
|
||||
if cfg.Model.IsDefined() {
|
||||
@ -26,8 +31,6 @@ func Generate(cfg *config.Config, option ...Option) error {
|
||||
plugins = append(plugins, resolvergen.New())
|
||||
if cfg.Federation.IsDefined() {
|
||||
if cfg.Federation.Version == 0 { // default to using the user's choice of version, but if unset, try to sort out which federation version to use
|
||||
urlRegex := regexp.MustCompile(`(?s)@link.*\(.*url:.*?"(.*?)"[^)]+\)`) // regex to grab the url of a link directive, should it exist
|
||||
versionRegex := regexp.MustCompile(`v(\d+).(\d+)$`) // regex to grab the version number from a url
|
||||
// check the sources, and if one is marked as federation v2, we mark the entirety to be generated using that format
|
||||
for _, v := range cfg.Sources {
|
||||
cfg.Federation.Version = 1
|
||||
|
||||
91
vendor/github.com/99designs/gqlgen/codegen/config/binder.go
generated
vendored
91
vendor/github.com/99designs/gqlgen/codegen/config/binder.go
generated
vendored
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"golang.org/x/tools/go/packages"
|
||||
@ -204,6 +205,7 @@ type TypeReference struct {
|
||||
IsContext bool // Is the Marshaler/Unmarshaller the context version; applies to either the method or interface variety.
|
||||
PointersInUmarshalInput bool // Inverse values and pointers in return.
|
||||
IsRoot bool // Is the type a root level definition such as Query, Mutation or Subscription
|
||||
EnumValues []EnumValueReference
|
||||
}
|
||||
|
||||
func (ref *TypeReference) Elem() *TypeReference {
|
||||
@ -321,6 +323,10 @@ func (ref *TypeReference) IsTargetNilable() bool {
|
||||
return IsNilable(ref.Target)
|
||||
}
|
||||
|
||||
func (ref *TypeReference) HasEnumValues() bool {
|
||||
return len(ref.EnumValues) > 0
|
||||
}
|
||||
|
||||
func (b *Binder) PushRef(ret *TypeReference) {
|
||||
b.References = append(b.References, ret)
|
||||
}
|
||||
@ -428,7 +434,12 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fun, isFunc := obj.(*types.Func); isFunc {
|
||||
if values := b.enumValues(def); len(values) > 0 {
|
||||
err = b.enumReference(ref, obj, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if fun, isFunc := obj.(*types.Func); isFunc {
|
||||
ref.GO = fun.Type().(*types.Signature).Params().At(0).Type()
|
||||
ref.IsContext = fun.Type().(*types.Signature).Results().At(0).Type().String() == "github.com/99designs/gqlgen/graphql.ContextMarshaler"
|
||||
ref.Marshaler = fun
|
||||
@ -548,3 +559,81 @@ func basicUnderlying(it types.Type) *types.Basic {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type EnumValueReference struct {
|
||||
Definition *ast.EnumValueDefinition
|
||||
Object types.Object
|
||||
}
|
||||
|
||||
func (b *Binder) enumValues(def *ast.Definition) map[string]EnumValue {
|
||||
if def.Kind != ast.Enum {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(def.Name, "__") {
|
||||
return nil
|
||||
}
|
||||
|
||||
model, ok := b.cfg.Models[def.Name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return model.EnumValues
|
||||
}
|
||||
|
||||
func (b *Binder) enumReference(ref *TypeReference, obj types.Object, values map[string]EnumValue) error {
|
||||
if len(ref.Definition.EnumValues) != len(values) {
|
||||
return fmt.Errorf("not all enum values are binded for %v", ref.Definition.Name)
|
||||
}
|
||||
|
||||
if fn, ok := obj.Type().(*types.Signature); ok {
|
||||
ref.GO = fn.Params().At(0).Type()
|
||||
} else {
|
||||
ref.GO = obj.Type()
|
||||
}
|
||||
|
||||
str, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref.Marshaler = str.Marshaler
|
||||
ref.Unmarshaler = str.Unmarshaler
|
||||
ref.EnumValues = make([]EnumValueReference, 0, len(values))
|
||||
|
||||
for _, value := range ref.Definition.EnumValues {
|
||||
v, ok := values[value.Name]
|
||||
if !ok {
|
||||
return fmt.Errorf("enum value not found for: %v, of enum: %v", value.Name, ref.Definition.Name)
|
||||
}
|
||||
|
||||
pkgName, typeName := code.PkgAndType(v.Value)
|
||||
if pkgName == "" {
|
||||
return fmt.Errorf("missing package name for %v", value.Name)
|
||||
}
|
||||
|
||||
valueObj, err := b.FindObject(pkgName, typeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !types.AssignableTo(valueObj.Type(), ref.GO) {
|
||||
return fmt.Errorf("wrong type: %v, for enum value: %v, expected type: %v, of enum: %v",
|
||||
valueObj.Type(), value.Name, ref.GO, ref.Definition.Name)
|
||||
}
|
||||
|
||||
switch valueObj.(type) {
|
||||
case *types.Const, *types.Var:
|
||||
ref.EnumValues = append(ref.EnumValues, EnumValueReference{
|
||||
Definition: value,
|
||||
Object: valueObj,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("unsupported enum value for: %v, of enum: %v, only const and var allowed",
|
||||
value.Name, ref.Definition.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
97
vendor/github.com/99designs/gqlgen/codegen/config/config.go
generated
vendored
97
vendor/github.com/99designs/gqlgen/codegen/config/config.go
generated
vendored
@ -275,6 +275,10 @@ func (c *Config) injectTypesFromSchema() error {
|
||||
SkipRuntime: true,
|
||||
}
|
||||
|
||||
c.Directives["goExtraField"] = DirectiveConfig{
|
||||
SkipRuntime: true,
|
||||
}
|
||||
|
||||
c.Directives["goField"] = DirectiveConfig{
|
||||
SkipRuntime: true,
|
||||
}
|
||||
@ -283,6 +287,10 @@ func (c *Config) injectTypesFromSchema() error {
|
||||
SkipRuntime: true,
|
||||
}
|
||||
|
||||
c.Directives["goEnum"] = DirectiveConfig{
|
||||
SkipRuntime: true,
|
||||
}
|
||||
|
||||
for _, schemaType := range c.Schema.Types {
|
||||
if c.IsRoot(schemaType) {
|
||||
continue
|
||||
@ -342,6 +350,82 @@ func (c *Config) injectTypesFromSchema() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if efds := schemaType.Directives.ForNames("goExtraField"); len(efds) != 0 {
|
||||
for _, efd := range efds {
|
||||
if fn := efd.Arguments.ForName("name"); fn != nil {
|
||||
extraFieldName := ""
|
||||
if fnv, err := fn.Value.Value(nil); err == nil {
|
||||
extraFieldName = fnv.(string)
|
||||
}
|
||||
|
||||
if extraFieldName == "" {
|
||||
return fmt.Errorf(
|
||||
"argument 'name' for directive @goExtraField (src: %s, line: %d) cannot by empty",
|
||||
efd.Position.Src.Name,
|
||||
efd.Position.Line,
|
||||
)
|
||||
}
|
||||
|
||||
extraField := ModelExtraField{}
|
||||
if t := efd.Arguments.ForName("type"); t != nil {
|
||||
if tv, err := t.Value.Value(nil); err == nil {
|
||||
extraField.Type = tv.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if extraField.Type == "" {
|
||||
return fmt.Errorf(
|
||||
"argument 'type' for directive @goExtraField (src: %s, line: %d) cannot by empty",
|
||||
efd.Position.Src.Name,
|
||||
efd.Position.Line,
|
||||
)
|
||||
}
|
||||
|
||||
if ot := efd.Arguments.ForName("overrideTags"); ot != nil {
|
||||
if otv, err := ot.Value.Value(nil); err == nil {
|
||||
extraField.OverrideTags = otv.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if d := efd.Arguments.ForName("description"); d != nil {
|
||||
if dv, err := d.Value.Value(nil); err == nil {
|
||||
extraField.Description = dv.(string)
|
||||
}
|
||||
}
|
||||
|
||||
typeMapEntry := c.Models[schemaType.Name]
|
||||
if typeMapEntry.ExtraFields == nil {
|
||||
typeMapEntry.ExtraFields = make(map[string]ModelExtraField)
|
||||
}
|
||||
|
||||
c.Models[schemaType.Name] = typeMapEntry
|
||||
c.Models[schemaType.Name].ExtraFields[extraFieldName] = extraField
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if schemaType.Kind == ast.Enum && !strings.HasPrefix(schemaType.Name, "__") {
|
||||
values := make(map[string]EnumValue)
|
||||
|
||||
for _, value := range schemaType.EnumValues {
|
||||
if directive := value.Directives.ForName("goEnum"); directive != nil {
|
||||
if arg := directive.Arguments.ForName("value"); arg != nil {
|
||||
if v, err := arg.Value.Value(nil); err == nil {
|
||||
values[value.Name] = EnumValue{
|
||||
Value: v.(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
model := c.Models[schemaType.Name]
|
||||
model.EnumValues = values
|
||||
c.Models[schemaType.Name] = model
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,6 +436,7 @@ type TypeMapEntry struct {
|
||||
Model StringList `yaml:"model,omitempty"`
|
||||
ForceGenerate bool `yaml:"forceGenerate,omitempty"`
|
||||
Fields map[string]TypeMapField `yaml:"fields,omitempty"`
|
||||
EnumValues map[string]EnumValue `yaml:"enum_values,omitempty"`
|
||||
|
||||
// Key is the Go name of the field.
|
||||
ExtraFields map[string]ModelExtraField `yaml:"extraFields,omitempty"`
|
||||
@ -363,6 +448,10 @@ type TypeMapField struct {
|
||||
GeneratedMethod string `yaml:"-"`
|
||||
}
|
||||
|
||||
type EnumValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type ModelExtraField struct {
|
||||
// Type is the Go type of the field.
|
||||
//
|
||||
@ -518,6 +607,14 @@ func (tm TypeMap) Check() error {
|
||||
return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model)
|
||||
}
|
||||
}
|
||||
|
||||
if len(entry.Model) == 0 {
|
||||
for enum, v := range entry.EnumValues {
|
||||
if v.Value != "" {
|
||||
return fmt.Errorf("model is empty for: %v, but enum value is specified for %v", typeName, enum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
1
vendor/github.com/99designs/gqlgen/codegen/directive.go
generated
vendored
1
vendor/github.com/99designs/gqlgen/codegen/directive.go
generated
vendored
@ -124,7 +124,6 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
|
||||
DirectiveDefinition: list[i].Definition,
|
||||
Builtin: b.Config.Directives[d.Name].SkipRuntime,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/codegen/field.go
generated
vendored
2
vendor/github.com/99designs/gqlgen/codegen/field.go
generated
vendored
@ -287,7 +287,7 @@ func (b *builder) findBindStructTagTarget(in types.Type, name string) (types.Obj
|
||||
tags := reflect.StructTag(t.Tag(i))
|
||||
if val, ok := tags.Lookup(b.Config.StructTag); ok && equalFieldName(val, name) {
|
||||
if found != nil {
|
||||
return nil, fmt.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", b.Config.StructTag, val)
|
||||
return nil, fmt.Errorf("tag %s is ambiguous; multiple fields have the same tag value of %s", b.Config.StructTag, val)
|
||||
}
|
||||
|
||||
found = field
|
||||
|
||||
4
vendor/github.com/99designs/gqlgen/codegen/field.gotpl
generated
vendored
4
vendor/github.com/99designs/gqlgen/codegen/field.gotpl
generated
vendored
@ -21,7 +21,7 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
|
||||
res := &{{ $field.TypeReference.Elem.GO | ref }}{}
|
||||
{{- else }}
|
||||
res := {{ $field.TypeReference.GO | ref }}{}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
fc.Result = res
|
||||
return ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res)
|
||||
{{- else}}
|
||||
@ -72,7 +72,7 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func (ec *executionContext) {{ $field.FieldContextFunc }}(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args }}_{{ else }}ctx{{ end }} context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: {{quote $field.Object.Name}},
|
||||
Field: field,
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/codegen/object.gotpl
generated
vendored
2
vendor/github.com/99designs/gqlgen/codegen/object.gotpl
generated
vendored
@ -48,7 +48,7 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
|
||||
{{- if $field.IsConcurrent }}
|
||||
field := field
|
||||
|
||||
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
|
||||
innerFunc := func(ctx context.Context, {{ if $field.TypeReference.GQL.NonNull }}fs{{ else }}_{{ end }} *graphql.FieldSet) (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
|
||||
10
vendor/github.com/99designs/gqlgen/codegen/templates/templates.go
generated
vendored
10
vendor/github.com/99designs/gqlgen/codegen/templates/templates.go
generated
vendored
@ -201,6 +201,7 @@ func Funcs() template.FuncMap {
|
||||
"rawQuote": rawQuote,
|
||||
"dump": Dump,
|
||||
"ref": ref,
|
||||
"obj": obj,
|
||||
"ts": TypeIdentifier,
|
||||
"call": Call,
|
||||
"prefixLines": prefixLines,
|
||||
@ -247,6 +248,15 @@ func ref(p types.Type) string {
|
||||
return CurrentImports.LookupType(p)
|
||||
}
|
||||
|
||||
func obj(obj types.Object) string {
|
||||
pkg := CurrentImports.Lookup(obj.Pkg().Path())
|
||||
if pkg != "" {
|
||||
pkg += "."
|
||||
}
|
||||
|
||||
return pkg + obj.Name()
|
||||
}
|
||||
|
||||
func Call(p *types.Func) string {
|
||||
pkg := CurrentImports.Lookup(p.Pkg().Path())
|
||||
|
||||
|
||||
31
vendor/github.com/99designs/gqlgen/codegen/type.gotpl
generated
vendored
31
vendor/github.com/99designs/gqlgen/codegen/type.gotpl
generated
vendored
@ -34,7 +34,10 @@
|
||||
return &pres, nil
|
||||
{{- else }}
|
||||
{{- if $type.Unmarshaler }}
|
||||
{{- if $type.CastType }}
|
||||
{{- if $type.HasEnumValues }}
|
||||
tmp, err := {{ $type.Unmarshaler | call }}(v)
|
||||
res := {{ $type.UnmarshalFunc }}[tmp]
|
||||
{{- else if $type.CastType }}
|
||||
{{- if $type.IsContext }}
|
||||
tmp, err := {{ $type.Unmarshaler | call }}(ctx, v)
|
||||
{{- else }}
|
||||
@ -170,7 +173,12 @@
|
||||
{{- else if and (not $type.IsTargetNilable) $type.IsNilable }}
|
||||
{{- $v = "*v" }}
|
||||
{{- end }}
|
||||
res := {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}({{ $v }}){{else}}{{ $v }}{{- end }})
|
||||
{{- if $type.HasEnumValues }}
|
||||
{{- $v = printf "%v[%v]" $type.MarshalFunc $v }}
|
||||
{{- else if $type.CastType }}
|
||||
{{- $v = printf "%v(%v)" ($type.CastType | ref) $v}}
|
||||
{{- end }}
|
||||
res := {{ $type.Marshaler | call }}({{ $v }})
|
||||
{{- if $type.GQL.NonNull }}
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
@ -196,4 +204,23 @@
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{- if $type.HasEnumValues }}
|
||||
{{- $enum := $type.GO }}
|
||||
{{- if $type.IsNilable }}
|
||||
{{- $enum = $type.GO.Elem }}
|
||||
{{- end }}
|
||||
var (
|
||||
{{ $type.UnmarshalFunc }} = map[string]{{ $enum | ref }}{
|
||||
{{- range $value := $type.EnumValues }}
|
||||
"{{ $value.Definition.Name }}": {{ $value.Object | obj }},
|
||||
{{- end }}
|
||||
}
|
||||
{{ $type.MarshalFunc }} = map[{{ $enum | ref }}]string{
|
||||
{{- range $value := $type.EnumValues }}
|
||||
{{ $value.Object | obj }}: "{{ $value.Definition.Name }}",
|
||||
{{- end }}
|
||||
}
|
||||
)
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
3
vendor/github.com/99designs/gqlgen/generate_examples.sh
generated
vendored
3
vendor/github.com/99designs/gqlgen/generate_examples.sh
generated
vendored
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
cd ./_examples
|
||||
go generate ./... || return 0
|
||||
1
vendor/github.com/99designs/gqlgen/graphql/context_path.go
generated
vendored
1
vendor/github.com/99designs/gqlgen/graphql/context_path.go
generated
vendored
@ -34,7 +34,6 @@ func (fic *PathContext) Path() ast.Path {
|
||||
if fic.ParentField != nil {
|
||||
fieldPath := fic.ParentField.Path()
|
||||
return append(fieldPath, path...)
|
||||
|
||||
}
|
||||
|
||||
return path
|
||||
|
||||
4
vendor/github.com/99designs/gqlgen/graphql/handler/server.go
generated
vendored
4
vendor/github.com/99designs/gqlgen/graphql/handler/server.go
generated
vendored
@ -107,7 +107,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
resp := &graphql.Response{Errors: []*gqlerror.Error{gqlErr}}
|
||||
b, _ := json.Marshal(resp)
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
w.Write(b)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -128,7 +128,7 @@ func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Write(b)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
|
||||
func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/graphql/handler/transport/error.go
generated
vendored
2
vendor/github.com/99designs/gqlgen/graphql/handler/transport/error.go
generated
vendored
@ -18,7 +18,7 @@ func SendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Write(b)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
|
||||
// SendErrorf wraps SendError to add formatted messages
|
||||
|
||||
8
vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go
generated
vendored
8
vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go
generated
vendored
@ -103,7 +103,7 @@ func (t Websocket) Do(w http.ResponseWriter, r *http.Request, exec graphql.Graph
|
||||
switch ws.Subprotocol() {
|
||||
default:
|
||||
msg := websocket.FormatCloseMessage(websocket.CloseProtocolError, fmt.Sprintf("unsupported negotiated subprotocol %s", ws.Subprotocol()))
|
||||
ws.WriteMessage(websocket.CloseMessage, msg)
|
||||
_ = ws.WriteMessage(websocket.CloseMessage, msg)
|
||||
return
|
||||
case graphqlwsSubprotocol, "":
|
||||
// clients are required to send a subprotocol, to be backward compatible with the previous implementation we select
|
||||
@ -272,7 +272,7 @@ func (c *wsConnection) run() {
|
||||
if !c.MissingPongOk {
|
||||
// Note: when the connection is closed by this deadline, the client
|
||||
// will receive an "invalid close code"
|
||||
c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval))
|
||||
_ = c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval))
|
||||
}
|
||||
go c.ping(ctx)
|
||||
}
|
||||
@ -312,7 +312,7 @@ func (c *wsConnection) run() {
|
||||
c.receivedPong = true
|
||||
c.mu.Unlock()
|
||||
// Clear ReadTimeout -- 0 time val clears.
|
||||
c.conn.SetReadDeadline(time.Time{})
|
||||
_ = c.conn.SetReadDeadline(time.Time{})
|
||||
default:
|
||||
c.sendConnectionError("unexpected message %s", m.t)
|
||||
c.close(websocket.CloseProtocolError, "unexpected message")
|
||||
@ -357,7 +357,7 @@ func (c *wsConnection) ping(ctx context.Context) {
|
||||
// if we have not yet received a pong, don't reset the deadline.
|
||||
c.mu.Lock()
|
||||
if !c.MissingPongOk && c.receivedPong {
|
||||
c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval))
|
||||
_ = c.conn.SetReadDeadline(time.Now().UTC().Add(2 * c.PingPongInterval))
|
||||
}
|
||||
c.receivedPong = false
|
||||
c.mu.Unlock()
|
||||
|
||||
29
vendor/github.com/99designs/gqlgen/graphql/id.go
generated
vendored
29
vendor/github.com/99designs/gqlgen/graphql/id.go
generated
vendored
@ -56,3 +56,32 @@ func UnmarshalIntID(v interface{}) (int, error) {
|
||||
return 0, fmt.Errorf("%T is not an int", v)
|
||||
}
|
||||
}
|
||||
|
||||
func MarshalUintID(i uint) Marshaler {
|
||||
return WriterFunc(func(w io.Writer) {
|
||||
writeQuotedString(w, strconv.FormatUint(uint64(i), 10))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalUintID(v interface{}) (uint, error) {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
result, err := strconv.ParseUint(v, 10, 64)
|
||||
return uint(result), err
|
||||
case int:
|
||||
return uint(v), nil
|
||||
case int64:
|
||||
return uint(v), nil
|
||||
case int32:
|
||||
return uint(v), nil
|
||||
case uint32:
|
||||
return uint(v), nil
|
||||
case uint64:
|
||||
return uint(v), nil
|
||||
case json.Number:
|
||||
result, err := strconv.ParseUint(string(v), 10, 64)
|
||||
return uint(result), err
|
||||
default:
|
||||
return 0, fmt.Errorf("%T is not an uint", v)
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/graphql/version.go
generated
vendored
2
vendor/github.com/99designs/gqlgen/graphql/version.go
generated
vendored
@ -1,3 +1,3 @@
|
||||
package graphql
|
||||
|
||||
const Version = "v0.17.45"
|
||||
const Version = "v0.17.47"
|
||||
|
||||
20
vendor/github.com/99designs/gqlgen/internal/code/packages.go
generated
vendored
20
vendor/github.com/99designs/gqlgen/internal/code/packages.go
generated
vendored
@ -21,12 +21,10 @@ var (
|
||||
|
||||
var mode = packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedImports |
|
||||
packages.NeedTypes |
|
||||
packages.NeedSyntax |
|
||||
packages.NeedTypesInfo |
|
||||
packages.NeedModule |
|
||||
packages.NeedDeps
|
||||
packages.NeedModule
|
||||
|
||||
type (
|
||||
// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages
|
||||
@ -135,11 +133,6 @@ func (p *Packages) LoadAll(importPaths ...string) []*packages.Package {
|
||||
func (p *Packages) addToCache(pkg *packages.Package) {
|
||||
imp := NormalizeVendor(pkg.PkgPath)
|
||||
p.packages[imp] = pkg
|
||||
for _, imp := range pkg.Imports {
|
||||
if _, found := p.packages[NormalizeVendor(imp.PkgPath)]; !found {
|
||||
p.addToCache(imp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load works the same as LoadAll, except a single package at a time.
|
||||
@ -220,18 +213,9 @@ func (p *Packages) NameForPackage(importPath string) string {
|
||||
return pkg.Name
|
||||
}
|
||||
|
||||
// Evict removes a given package import path from the cache, along with any packages that depend on it. Further calls
|
||||
// to Load will fetch it from disk.
|
||||
// Evict removes a given package import path from the cache. Further calls to Load will fetch it from disk.
|
||||
func (p *Packages) Evict(importPath string) {
|
||||
delete(p.packages, importPath)
|
||||
|
||||
for _, pkg := range p.packages {
|
||||
for _, imported := range pkg.Imports {
|
||||
if imported.PkgPath == importPath {
|
||||
p.Evict(pkg.PkgPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Packages) ModTidy() error {
|
||||
|
||||
1
vendor/github.com/99designs/gqlgen/internal/rewrite/rewriter.go
generated
vendored
1
vendor/github.com/99designs/gqlgen/internal/rewrite/rewriter.go
generated
vendored
@ -63,7 +63,6 @@ func (r *Rewriter) getFile(filename string) string {
|
||||
}
|
||||
|
||||
r.files[filename] = string(b)
|
||||
|
||||
}
|
||||
|
||||
return r.files[filename]
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/main.go
generated
vendored
2
vendor/github.com/99designs/gqlgen/main.go
generated
vendored
@ -1,6 +1,6 @@
|
||||
package main
|
||||
|
||||
//go:generate sh generate_examples.sh
|
||||
//go:generate sh -c "cd _examples && go generate ./..."
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
3
vendor/github.com/99designs/gqlgen/plugin/federation/federation.go
generated
vendored
3
vendor/github.com/99designs/gqlgen/plugin/federation/federation.go
generated
vendored
@ -182,7 +182,6 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
|
||||
|
||||
var entities, resolvers, entityResolverInputDefinitions string
|
||||
for _, e := range f.Entities {
|
||||
|
||||
if e.Def.Kind != ast.Interface {
|
||||
if entities != "" {
|
||||
entities += " | "
|
||||
@ -329,7 +328,6 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
|
||||
|
||||
// add type info to entity
|
||||
e.Type = obj.Type
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,7 +414,6 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return templates.Render(templates.Options{
|
||||
|
||||
13
vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl
generated
vendored
13
vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl
generated
vendored
@ -253,19 +253,30 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
|
||||
ok bool
|
||||
)
|
||||
_ = val
|
||||
// if all of the KeyFields values for this resolver are null,
|
||||
// we shouldn't use use it
|
||||
allNull := true
|
||||
{{- range $_, $keyField := .KeyFields }}
|
||||
m = rep
|
||||
{{- range $i, $field := .Field }}
|
||||
if {{ if (ne $i $keyField.Field.LastIndex ) -}}val{{- else -}}_{{- end -}}, ok = m["{{.}}"]; !ok {
|
||||
val, ok = m["{{.}}"]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
{{- if (ne $i $keyField.Field.LastIndex ) }}
|
||||
if m, ok = val.(map[string]interface{}); !ok {
|
||||
break
|
||||
}
|
||||
{{- else}}
|
||||
if allNull {
|
||||
allNull = val == nil
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end }}
|
||||
if allNull {
|
||||
break
|
||||
}
|
||||
return "{{.ResolverName}}", nil
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
1
vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go
generated
vendored
1
vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go
generated
vendored
@ -176,7 +176,6 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
|
||||
uniqueMap[iface] = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
b.Models = append(b.Models, it)
|
||||
|
||||
2
vendor/github.com/99designs/gqlgen/plugin/plugin.go
generated
vendored
2
vendor/github.com/99designs/gqlgen/plugin/plugin.go
generated
vendored
@ -33,5 +33,5 @@ type LateSourceInjector interface {
|
||||
|
||||
// ResolverImplementer is used to generate code inside resolvers
|
||||
type ResolverImplementer interface {
|
||||
Implement(field *codegen.Field) string
|
||||
Implement(prevImplementation string, field *codegen.Field) string
|
||||
}
|
||||
|
||||
16
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go
generated
vendored
16
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go
generated
vendored
@ -127,14 +127,9 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
|
||||
if !f.IsResolver {
|
||||
continue
|
||||
}
|
||||
|
||||
structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
|
||||
comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`))
|
||||
implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
|
||||
if implementation == "" {
|
||||
// use default implementation, if no implementation was previously used
|
||||
implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v - %v\"))", f.GoFieldName, f.Name)
|
||||
}
|
||||
resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil}
|
||||
var implExists bool
|
||||
for _, p := range data.Plugins {
|
||||
@ -257,13 +252,20 @@ type Resolver struct {
|
||||
PrevDecl *ast.FuncDecl
|
||||
Comment string
|
||||
ImplementationStr string
|
||||
ImplementationRender func(r *codegen.Field) string
|
||||
ImplementationRender func(prevImplementation string, r *codegen.Field) string
|
||||
}
|
||||
|
||||
func (r *Resolver) Implementation() string {
|
||||
if r.ImplementationRender != nil {
|
||||
return r.ImplementationRender(r.Field)
|
||||
// use custom implementation
|
||||
return r.ImplementationRender(r.ImplementationStr, r.Field)
|
||||
}
|
||||
// if not implementation was previously used, use default implementation
|
||||
if r.ImplementationStr == "" {
|
||||
// use default implementation, if no implementation was previously used
|
||||
return fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v - %v\"))", r.Field.GoFieldName, r.Field.Name)
|
||||
}
|
||||
// use previously used implementation
|
||||
return r.ImplementationStr
|
||||
}
|
||||
|
||||
|
||||
53
vendor/github.com/caddyserver/certmagic/README.md
generated
vendored
53
vendor/github.com/caddyserver/certmagic/README.md
generated
vendored
@ -60,13 +60,16 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- [Advanced use](#advanced-use)
|
||||
- [Wildcard Certificates](#wildcard-certificates)
|
||||
- [Behind a load balancer (or in a cluster)](#behind-a-load-balancer-or-in-a-cluster)
|
||||
- [The ACME Challenges](#the-acme-challenges)
|
||||
- [HTTP Challenge](#http-challenge)
|
||||
- [TLS-ALPN Challenge](#tls-alpn-challenge)
|
||||
- [DNS Challenge](#dns-challenge)
|
||||
- [On-Demand TLS](#on-demand-tls)
|
||||
- [Storage](#storage)
|
||||
- [Cache](#cache)
|
||||
- [The ACME Challenges](#the-acme-challenges)
|
||||
- [HTTP Challenge](#http-challenge)
|
||||
- [TLS-ALPN Challenge](#tls-alpn-challenge)
|
||||
- [DNS Challenge](#dns-challenge)
|
||||
- [On-Demand TLS](#on-demand-tls)
|
||||
- [Storage](#storage)
|
||||
- [Cache](#cache)
|
||||
- [Events](#events)
|
||||
- [ZeroSSL](#zerossl)
|
||||
- [FAQ](#faq)
|
||||
- [Contributing](#contributing)
|
||||
- [Project History](#project-history)
|
||||
- [Credits and License](#credits-and-license)
|
||||
@ -87,7 +90,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- Exponential backoff with carefully-tuned intervals
|
||||
- Retries with optional test/staging CA endpoint instead of production, to avoid rate limits
|
||||
- Written in Go, a language with memory-safety guarantees
|
||||
- Powered by [ACMEz](https://github.com/mholt/acmez), _the_ premier ACME client library for Go
|
||||
- Powered by [ACMEz](https://github.com/mholt/acmez/v2), _the_ premier ACME client library for Go
|
||||
- All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
|
||||
- Pluggable storage backends (default: file system)
|
||||
- Pluggable key sources
|
||||
@ -110,6 +113,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
- Cross-platform support! Mac, Windows, Linux, BSD, Android...
|
||||
- Scales to hundreds of thousands of names/certificates per instance
|
||||
- Use in conjunction with your own certificates
|
||||
- Full support for [draft-ietf-acme-ari](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/) (ACME Renewal Information; ARI) extension
|
||||
|
||||
|
||||
## Requirements
|
||||
@ -125,6 +129,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
|
||||
4. Persistent storage
|
||||
- Typically the local file system (default)
|
||||
- Other integrations available/possible
|
||||
5. Go 1.21 or newer
|
||||
|
||||
**_Before using this library, your domain names MUST be pointed (A/AAAA records) at your server (unless you use the DNS challenge)!_**
|
||||
|
||||
@ -292,7 +297,7 @@ tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos..
|
||||
// we can simply set its GetCertificate field and append the
|
||||
// TLS-ALPN challenge protocol to the NextProtos
|
||||
myTLSConfig.GetCertificate = magic.GetCertificate
|
||||
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protocol)
|
||||
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, acmez.ACMETLS1Protocol)
|
||||
|
||||
// the HTTP challenge has to be handled by your HTTP server;
|
||||
// if you don't have one, you should have disabled it earlier
|
||||
@ -379,7 +384,7 @@ Or make two simple changes to an existing `tls.Config`:
|
||||
|
||||
```go
|
||||
myTLSConfig.GetCertificate = magic.GetCertificate
|
||||
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, tlsalpn01.ACMETLS1Protocol}
|
||||
myTLSConfig.NextProtos = append(myTLSConfig.NextProtos, acmez.ACMETLS1Protocol}
|
||||
```
|
||||
|
||||
Then just make sure your TLS listener is listening on port 443:
|
||||
@ -401,8 +406,10 @@ To enable it, just set the `DNS01Solver` field on a `certmagic.ACMEIssuer` struc
|
||||
import "github.com/libdns/cloudflare"
|
||||
|
||||
certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{
|
||||
DNSProvider: &cloudflare.Provider{
|
||||
APIToken: "topsecret",
|
||||
DNSManager: certmagic.DNSManager{
|
||||
DNSProvider: &cloudflare.Provider{
|
||||
APIToken: "topsecret",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -504,6 +511,26 @@ CertMagic emits events when possible things of interest happen. Set the [`OnEven
|
||||
|
||||
`OnEvent` can return an error. Some events may be aborted by returning an error. For example, returning an error from `cert_obtained` can cancel obtaining the certificate. Only return an error from `OnEvent` if you want to abort program flow.
|
||||
|
||||
## ZeroSSL
|
||||
|
||||
ZeroSSL has both ACME and HTTP API services for getting certificates. CertMagic works with both of them.
|
||||
|
||||
To use ZeroSSL's ACME server, configure CertMagic with an [`ACMEIssuer`](https://pkg.go.dev/github.com/caddyserver/certmagic#ACMEIssuer) like you would with any other ACME CA (just adjust the directory URL). External Account Binding (EAB) is required for ZeroSSL. You can use the [ZeroSSL API](https://pkg.go.dev/github.com/caddyserver/zerossl) to generate one, or your account dashboard.
|
||||
|
||||
To use ZeroSSL's API instead, use the [`ZeroSSLIssuer`](https://pkg.go.dev/github.com/caddyserver/certmagic#ZeroSSLIssuer). Here is a simple example:
|
||||
|
||||
```go
|
||||
magic := certmagic.NewDefault()
|
||||
|
||||
magic.Issuers = []certmagic.Issuer{
|
||||
certmagic.ZeroSSLIssuer{
|
||||
APIKey: "<your ZeroSSL API key>",
|
||||
}),
|
||||
}
|
||||
|
||||
err := magic.ManageSync(ctx, []string{"example.com"})
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can I use some of my own certificates while using CertMagic?
|
||||
@ -540,7 +567,7 @@ We welcome your contributions! Please see our **[contributing guidelines](https:
|
||||
|
||||
## Project History
|
||||
|
||||
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [ACMEz](https://github.com/mholt/acmez). CertMagic's code was originally a central part of Caddy even before Let's Encrypt entered public beta in 2015.
|
||||
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [ACMEz](https://github.com/mholt/acmez/v2). CertMagic's code was originally a central part of Caddy even before Let's Encrypt entered public beta in 2015.
|
||||
|
||||
In the years since then, Caddy's TLS automation techniques have been widely adopted, tried and tested in production, and served millions of sites and secured trillions of connections.
|
||||
|
||||
|
||||
34
vendor/github.com/caddyserver/certmagic/account.go
generated
vendored
34
vendor/github.com/caddyserver/certmagic/account.go
generated
vendored
@ -32,7 +32,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
)
|
||||
|
||||
// getAccount either loads or creates a new account, depending on if
|
||||
@ -88,11 +88,18 @@ func (*ACMEIssuer) newAccount(email string) (acme.Account, error) {
|
||||
// If it does not exist in storage, it will be retrieved from the ACME server and added to storage.
|
||||
// The account must already exist; it does not create a new account.
|
||||
func (am *ACMEIssuer) GetAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
|
||||
account, err := am.loadAccountByKey(ctx, privateKeyPEM)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
account, err = am.lookUpAccount(ctx, privateKeyPEM)
|
||||
email := am.getEmail()
|
||||
if email == "" {
|
||||
if account, err := am.loadAccountByKey(ctx, privateKeyPEM); err == nil {
|
||||
return account, nil
|
||||
}
|
||||
} else {
|
||||
keyBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserPrivateKey(am.CA, email))
|
||||
if err == nil && bytes.Equal(bytes.TrimSpace(keyBytes), bytes.TrimSpace(privateKeyPEM)) {
|
||||
return am.loadAccount(ctx, am.CA, email)
|
||||
}
|
||||
}
|
||||
return account, err
|
||||
return am.lookUpAccount(ctx, privateKeyPEM)
|
||||
}
|
||||
|
||||
// loadAccountByKey loads the account with the given private key from storage, if it exists.
|
||||
@ -107,9 +114,14 @@ func (am *ACMEIssuer) loadAccountByKey(ctx context.Context, privateKeyPEM []byte
|
||||
email := path.Base(accountFolderKey)
|
||||
keyBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserPrivateKey(am.CA, email))
|
||||
if err != nil {
|
||||
return acme.Account{}, err
|
||||
// Try the next account: This one is missing its private key, if it turns out to be the one we're looking
|
||||
// for we will try to save it again after confirming with the ACME server.
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(bytes.TrimSpace(keyBytes), bytes.TrimSpace(privateKeyPEM)) {
|
||||
// Found the account with the correct private key, try loading it. If this fails we we will follow
|
||||
// the same procedure as if the private key was not found and confirm with the ACME server before saving
|
||||
// it again.
|
||||
return am.loadAccount(ctx, am.CA, email)
|
||||
}
|
||||
}
|
||||
@ -171,6 +183,16 @@ func (am *ACMEIssuer) saveAccount(ctx context.Context, ca string, account acme.A
|
||||
return storeTx(ctx, am.config.Storage, all)
|
||||
}
|
||||
|
||||
// deleteAccountLocally deletes the registration info and private key of the account
|
||||
// for the given CA from storage.
|
||||
func (am *ACMEIssuer) deleteAccountLocally(ctx context.Context, ca string, account acme.Account) error {
|
||||
primaryContact := getPrimaryContact(account)
|
||||
if err := am.config.Storage.Delete(ctx, am.storageKeyUserReg(ca, primaryContact)); err != nil {
|
||||
return err
|
||||
}
|
||||
return am.config.Storage.Delete(ctx, am.storageKeyUserPrivateKey(ca, primaryContact))
|
||||
}
|
||||
|
||||
// setEmail does everything it can to obtain an email address
|
||||
// from the user within the scope of memory and storage to use
|
||||
// for ACME TLS. If it cannot get an email address, it does nothing
|
||||
|
||||
129
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
129
vendor/github.com/caddyserver/certmagic/acmeclient.go
generated
vendored
@ -18,23 +18,19 @@ import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// acmeClient holds state necessary to perform ACME operations
|
||||
// for certificate management with an ACME account. Call
|
||||
// ACMEIssuer.newACMEClientWithAccount() to get a valid one.
|
||||
@ -141,44 +137,21 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
|
||||
// independent of any particular ACME account. If useTestCA is true, am.TestCA
|
||||
// will be used if it is set; otherwise, the primary CA will be used.
|
||||
func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
|
||||
// ensure defaults are filled in
|
||||
var caURL string
|
||||
if useTestCA {
|
||||
caURL = iss.TestCA
|
||||
client, err := iss.newBasicACMEClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = iss.CA
|
||||
}
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
|
||||
// fill in a little more beyond a basic client
|
||||
if useTestCA && iss.TestCA != "" {
|
||||
client.Client.Directory = iss.TestCA
|
||||
}
|
||||
certObtainTimeout := iss.CertObtainTimeout
|
||||
if certObtainTimeout == 0 {
|
||||
certObtainTimeout = DefaultACME.CertObtainTimeout
|
||||
}
|
||||
|
||||
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||
if !strings.Contains(caURL, "://") {
|
||||
caURL = "https://" + caURL
|
||||
}
|
||||
u, err := url.Parse(caURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "https" && !isLoopback(u.Host) && !isInternal(u.Host) {
|
||||
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
|
||||
}
|
||||
|
||||
client := &acmez.Client{
|
||||
Client: &acme.Client{
|
||||
Directory: caURL,
|
||||
PollTimeout: certObtainTimeout,
|
||||
UserAgent: buildUAString(),
|
||||
HTTPClient: iss.httpClient,
|
||||
},
|
||||
ChallengeSolvers: make(map[string]acmez.Solver),
|
||||
}
|
||||
client.Logger = iss.Logger.Named("acme_client")
|
||||
client.Client.PollTimeout = certObtainTimeout
|
||||
client.ChallengeSolvers = make(map[string]acmez.Solver)
|
||||
|
||||
// configure challenges (most of the time, DNS challenge is
|
||||
// exclusive of other ones because it is usually only used
|
||||
@ -186,38 +159,24 @@ func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
|
||||
if iss.DNS01Solver == nil {
|
||||
// enable HTTP-01 challenge
|
||||
if !iss.DisableHTTPChallenge {
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if iss.AltHTTPPort > 0 {
|
||||
useHTTPPort = iss.AltHTTPPort
|
||||
}
|
||||
client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{
|
||||
storage: iss.config.Storage,
|
||||
storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
|
||||
solver: &httpSolver{
|
||||
acmeIssuer: iss,
|
||||
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useHTTPPort)),
|
||||
handler: iss.HTTPChallengeHandler(http.NewServeMux()),
|
||||
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(iss.getHTTPPort())),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// enable TLS-ALPN-01 challenge
|
||||
if !iss.DisableTLSALPNChallenge {
|
||||
useTLSALPNPort := TLSALPNChallengePort
|
||||
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
|
||||
useTLSALPNPort = HTTPSPort
|
||||
}
|
||||
if iss.AltTLSALPNPort > 0 {
|
||||
useTLSALPNPort = iss.AltTLSALPNPort
|
||||
}
|
||||
client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{
|
||||
storage: iss.config.Storage,
|
||||
storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
|
||||
solver: &tlsALPNSolver{
|
||||
config: iss.config,
|
||||
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useTLSALPNPort)),
|
||||
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(iss.getTLSALPNPort())),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -248,6 +207,64 @@ func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// newBasicACMEClient sets up a basically-functional ACME client that is not capable
|
||||
// of solving challenges but can provide basic interactions with the server.
|
||||
func (iss *ACMEIssuer) newBasicACMEClient() (*acmez.Client, error) {
|
||||
caURL := iss.CA
|
||||
if caURL == "" {
|
||||
caURL = DefaultACME.CA
|
||||
}
|
||||
// ensure endpoint is secure (assume HTTPS if scheme is missing)
|
||||
if !strings.Contains(caURL, "://") {
|
||||
caURL = "https://" + caURL
|
||||
}
|
||||
u, err := url.Parse(caURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "https" && !SubjectIsInternal(u.Host) {
|
||||
return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required for non-internal CA)", caURL)
|
||||
}
|
||||
return &acmez.Client{
|
||||
Client: &acme.Client{
|
||||
Directory: caURL,
|
||||
UserAgent: buildUAString(),
|
||||
HTTPClient: iss.httpClient,
|
||||
Logger: iss.Logger.Named("acme_client"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (iss *ACMEIssuer) getRenewalInfo(ctx context.Context, cert Certificate) (acme.RenewalInfo, error) {
|
||||
acmeClient, err := iss.newBasicACMEClient()
|
||||
if err != nil {
|
||||
return acme.RenewalInfo{}, err
|
||||
}
|
||||
return acmeClient.GetRenewalInfo(ctx, cert.Certificate.Leaf)
|
||||
}
|
||||
|
||||
func (iss *ACMEIssuer) getHTTPPort() int {
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if iss.AltHTTPPort > 0 {
|
||||
useHTTPPort = iss.AltHTTPPort
|
||||
}
|
||||
return useHTTPPort
|
||||
}
|
||||
|
||||
func (iss *ACMEIssuer) getTLSALPNPort() int {
|
||||
useTLSALPNPort := TLSALPNChallengePort
|
||||
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
|
||||
useTLSALPNPort = HTTPSPort
|
||||
}
|
||||
if iss.AltTLSALPNPort > 0 {
|
||||
useTLSALPNPort = iss.AltTLSALPNPort
|
||||
}
|
||||
return useTLSALPNPort
|
||||
}
|
||||
|
||||
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
|
||||
email := c.iss.getEmail()
|
||||
|
||||
|
||||
160
vendor/github.com/caddyserver/certmagic/acmeissuer.go
generated
vendored
160
vendor/github.com/caddyserver/certmagic/acmeissuer.go
generated
vendored
@ -1,3 +1,17 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
@ -14,8 +28,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@ -55,6 +69,13 @@ type ACMEIssuer struct {
|
||||
// with this ACME account
|
||||
ExternalAccount *acme.EAB
|
||||
|
||||
// Optionally specify the validity period of
|
||||
// the certificate(s) here as offsets from the
|
||||
// approximate time of certificate issuance,
|
||||
// but note that not all CAs support this
|
||||
// (EXPERIMENTAL: Subject to change)
|
||||
NotBefore, NotAfter time.Duration
|
||||
|
||||
// Disable all HTTP challenges
|
||||
DisableHTTPChallenge bool
|
||||
|
||||
@ -169,6 +190,12 @@ func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
|
||||
if template.ExternalAccount == nil {
|
||||
template.ExternalAccount = DefaultACME.ExternalAccount
|
||||
}
|
||||
if template.NotBefore == 0 {
|
||||
template.NotBefore = DefaultACME.NotBefore
|
||||
}
|
||||
if template.NotAfter == 0 {
|
||||
template.NotAfter = DefaultACME.NotAfter
|
||||
}
|
||||
if !template.DisableHTTPChallenge {
|
||||
template.DisableHTTPChallenge = DefaultACME.DisableHTTPChallenge
|
||||
}
|
||||
@ -296,14 +323,32 @@ func (iss *ACMEIssuer) isAgreed() bool {
|
||||
|
||||
// PreCheck performs a few simple checks before obtaining or
|
||||
// renewing a certificate with ACME, and returns whether this
|
||||
// batch is eligible for certificates if using Let's Encrypt.
|
||||
// It also ensures that an email address is available.
|
||||
// batch is eligible for certificates. It also ensures that an
|
||||
// email address is available if possible.
|
||||
//
|
||||
// IP certificates via ACME are defined in RFC 8738.
|
||||
func (am *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
|
||||
publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com") || strings.Contains(am.CA, "api.pki.goog")
|
||||
publicCAsAndIPCerts := map[string]bool{ // map of public CAs to whether they support IP certificates (last updated: Q1 2024)
|
||||
"api.letsencrypt.org": false, // https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
"acme.zerossl.com": false, // only supported via their API, not ACME endpoint
|
||||
"api.pki.goog": true, // https://pki.goog/faq/#faq-IPCerts
|
||||
"api.buypass.com": false, // https://community.buypass.com/t/h7hm76w/buypass-support-for-rfc-8738
|
||||
"acme.ssl.com": false,
|
||||
}
|
||||
var publicCA, ipCertAllowed bool
|
||||
for caSubstr, ipCert := range publicCAsAndIPCerts {
|
||||
if strings.Contains(am.CA, caSubstr) {
|
||||
publicCA, ipCertAllowed = true, ipCert
|
||||
break
|
||||
}
|
||||
}
|
||||
if publicCA {
|
||||
for _, name := range names {
|
||||
if !SubjectQualifiesForPublicCert(name) {
|
||||
return fmt.Errorf("subject does not qualify for a public certificate: %s", name)
|
||||
return fmt.Errorf("subject '%s' does not qualify for a public certificate", name)
|
||||
}
|
||||
if !ipCertAllowed && SubjectIsIP(name) {
|
||||
return fmt.Errorf("subject '%s' cannot have public IP certificate from %s (if CA's policy has changed, please notify the developers in an issue)", name, am.CA)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -317,12 +362,13 @@ func (am *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (
|
||||
panic("missing config pointer (must use NewACMEIssuer)")
|
||||
}
|
||||
|
||||
var isRetry bool
|
||||
if attempts, ok := ctx.Value(AttemptsCtxKey).(*int); ok {
|
||||
isRetry = *attempts > 0
|
||||
var attempts int
|
||||
if attemptsPtr, ok := ctx.Value(AttemptsCtxKey).(*int); ok {
|
||||
attempts = *attemptsPtr
|
||||
}
|
||||
isRetry := attempts > 0
|
||||
|
||||
cert, usedTestCA, err := am.doIssue(ctx, csr, isRetry)
|
||||
cert, usedTestCA, err := am.doIssue(ctx, csr, attempts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -350,7 +396,7 @@ func (am *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (
|
||||
// other endpoint. This is more likely to happen if a user is testing with
|
||||
// the staging CA as the main CA, then changes their configuration once they
|
||||
// think they are ready for the production endpoint.
|
||||
cert, _, err = am.doIssue(ctx, csr, false)
|
||||
cert, _, err = am.doIssue(ctx, csr, 0)
|
||||
if err != nil {
|
||||
// succeeded with test CA but failed just now with the production CA;
|
||||
// either we are observing differing internal states of each CA that will
|
||||
@ -378,7 +424,8 @@ func (am *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (
|
||||
return cert, err
|
||||
}
|
||||
|
||||
func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) {
|
||||
func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest, attempts int) (*IssuedCertificate, bool, error) {
|
||||
useTestCA := attempts > 0
|
||||
client, err := am.newACMEClientWithAccount(ctx, useTestCA, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@ -393,12 +440,72 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
|
||||
}
|
||||
}
|
||||
|
||||
certChains, err := client.acmeClient.ObtainCertificateUsingCSR(ctx, client.account, csr)
|
||||
params, err := acmez.OrderParametersFromCSR(client.account, csr)
|
||||
if err != nil {
|
||||
return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory)
|
||||
return nil, false, fmt.Errorf("generating order parameters from CSR: %v", err)
|
||||
}
|
||||
if len(certChains) == 0 {
|
||||
return nil, usingTestCA, fmt.Errorf("no certificate chains")
|
||||
if am.NotBefore != 0 {
|
||||
params.NotBefore = time.Now().Add(am.NotBefore)
|
||||
}
|
||||
if am.NotAfter != 0 {
|
||||
params.NotAfter = time.Now().Add(am.NotAfter)
|
||||
}
|
||||
|
||||
// Notify the ACME server we are replacing a certificate (if the caller says we are),
|
||||
// only if the following conditions are met:
|
||||
// - The caller has set a Replaces value in the context, indicating this is a renewal.
|
||||
// - Not using test CA. This should be obvious, but a test CA should be in a separate
|
||||
// environment from production, and thus not have knowledge of the cert being replaced.
|
||||
// - Not a certain attempt number. We skip setting Replaces once early on in the retries
|
||||
// in case the reason the order is failing is only because there is a state inconsistency
|
||||
// between client and server or some sort of bookkeeping error with regards to the certID
|
||||
// and the server is rejecting the ARI certID. In any case, an invalid certID may cause
|
||||
// orders to fail. So try once without setting it.
|
||||
if !usingTestCA && attempts != 2 {
|
||||
if replacing, ok := ctx.Value(ctxKeyARIReplaces).(*x509.Certificate); ok {
|
||||
params.Replaces = replacing
|
||||
}
|
||||
}
|
||||
|
||||
// do this in a loop because there's an error case that may necessitate a retry, but not more than once
|
||||
var certChains []acme.Certificate
|
||||
for i := 0; i < 2; i++ {
|
||||
am.Logger.Info("using ACME account",
|
||||
zap.String("account_id", params.Account.Location),
|
||||
zap.Strings("account_contact", params.Account.Contact))
|
||||
|
||||
certChains, err = client.acmeClient.ObtainCertificate(ctx, params)
|
||||
if err != nil {
|
||||
var prob acme.Problem
|
||||
if errors.As(err, &prob) && prob.Type == acme.ProblemTypeAccountDoesNotExist {
|
||||
am.Logger.Warn("ACME account does not exist on server; attempting to recreate",
|
||||
zap.String("account_id", client.account.Location),
|
||||
zap.Strings("account_contact", client.account.Contact),
|
||||
zap.String("key_location", am.storageKeyUserPrivateKey(client.acmeClient.Directory, am.getEmail())),
|
||||
zap.Object("problem", prob))
|
||||
|
||||
// the account we have no longer exists on the CA, so we need to create a new one;
|
||||
// we could use the same key pair, but this is a good opportunity to rotate keys
|
||||
// (see https://caddy.community/t/acme-account-is-not-regenerated-when-acme-server-gets-reinstalled/22627)
|
||||
// (basically this happens if the CA gets reset or reinstalled; usually just internal PKI)
|
||||
err := am.deleteAccountLocally(ctx, client.iss.CA, client.account)
|
||||
if err != nil {
|
||||
return nil, usingTestCA, fmt.Errorf("%v ACME account no longer exists on CA, but resetting our local copy of the account info failed: %v", nameSet, err)
|
||||
}
|
||||
|
||||
// recreate account and try again
|
||||
client, err = am.newACMEClientWithAccount(ctx, useTestCA, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory)
|
||||
}
|
||||
if len(certChains) == 0 {
|
||||
return nil, usingTestCA, fmt.Errorf("no certificate chains")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
preferredChain := am.selectPreferredChain(certChains)
|
||||
@ -408,6 +515,8 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
|
||||
Metadata: preferredChain,
|
||||
}
|
||||
|
||||
am.Logger.Debug("selected certificate chain", zap.String("url", preferredChain.URL))
|
||||
|
||||
return ic, usingTestCA, nil
|
||||
}
|
||||
|
||||
@ -523,16 +632,27 @@ var DefaultACME = ACMEIssuer{
|
||||
HTTPProxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
// Some well-known CA endpoints available to use.
|
||||
// Some well-known CA endpoints available to use. See
|
||||
// the documentation for each service; some may require
|
||||
// External Account Binding (EAB) and possibly payment.
|
||||
// COMPATIBILITY NOTICE: These constants refer to external
|
||||
// resources and are thus subject to change or removal
|
||||
// without a major version bump.
|
||||
const (
|
||||
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
ZeroSSLProductionCA = "https://acme.zerossl.com/v2/DV90"
|
||||
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory" // https://letsencrypt.org/docs/staging-environment/
|
||||
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory" // https://letsencrypt.org/getting-started/
|
||||
ZeroSSLProductionCA = "https://acme.zerossl.com/v2/DV90" // https://zerossl.com/documentation/acme/
|
||||
GoogleTrustStagingCA = "https://dv.acme-v02.test-api.pki.goog/directory" // https://cloud.google.com/certificate-manager/docs/public-ca-tutorial
|
||||
GoogleTrustProductionCA = "https://dv.acme-v02.api.pki.goog/directory" // https://cloud.google.com/certificate-manager/docs/public-ca-tutorial
|
||||
)
|
||||
|
||||
// prefixACME is the storage key prefix used for ACME-specific assets.
|
||||
const prefixACME = "acme"
|
||||
|
||||
type ctxKey string
|
||||
|
||||
const ctxKeyARIReplaces = ctxKey("ari_replaces")
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ PreChecker = (*ACMEIssuer)(nil)
|
||||
|
||||
21
vendor/github.com/caddyserver/certmagic/cache.go
generated
vendored
21
vendor/github.com/caddyserver/certmagic/cache.go
generated
vendored
@ -16,7 +16,7 @@ package certmagic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
weakrand "math/rand" // seeded elsewhere
|
||||
weakrand "math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -394,17 +394,26 @@ func (certCache *Cache) AllMatchingCertificates(name string) []Certificate {
|
||||
return certs
|
||||
}
|
||||
|
||||
// SubjectIssuer pairs a subject name with an issuer ID/key.
|
||||
type SubjectIssuer struct {
|
||||
Subject, IssuerKey string
|
||||
}
|
||||
|
||||
// RemoveManaged removes managed certificates for the given subjects from the cache.
|
||||
// This effectively stops maintenance of those certificates.
|
||||
func (certCache *Cache) RemoveManaged(subjects []string) {
|
||||
// This effectively stops maintenance of those certificates. If an IssuerKey is
|
||||
// specified alongside the subject, only certificates for that subject from the
|
||||
// specified issuer will be removed.
|
||||
func (certCache *Cache) RemoveManaged(subjects []SubjectIssuer) {
|
||||
deleteQueue := make([]string, 0, len(subjects))
|
||||
for _, subject := range subjects {
|
||||
certs := certCache.getAllMatchingCerts(subject) // does NOT expand wildcards; exact matches only
|
||||
for _, subj := range subjects {
|
||||
certs := certCache.getAllMatchingCerts(subj.Subject) // does NOT expand wildcards; exact matches only
|
||||
for _, cert := range certs {
|
||||
if !cert.managed {
|
||||
continue
|
||||
}
|
||||
deleteQueue = append(deleteQueue, cert.hash)
|
||||
if subj.IssuerKey == "" || cert.issuerKey == subj.IssuerKey {
|
||||
deleteQueue = append(deleteQueue, cert.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
certCache.Remove(deleteQueue)
|
||||
|
||||
230
vendor/github.com/caddyserver/certmagic/certificates.go
generated
vendored
230
vendor/github.com/caddyserver/certmagic/certificates.go
generated
vendored
@ -18,12 +18,15 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
@ -56,6 +59,9 @@ type Certificate struct {
|
||||
|
||||
// The unique string identifying the issuer of this certificate.
|
||||
issuerKey string
|
||||
|
||||
// ACME Renewal Information, if available
|
||||
ari acme.RenewalInfo
|
||||
}
|
||||
|
||||
// Empty returns true if the certificate struct is not filled out; at
|
||||
@ -67,10 +73,106 @@ func (cert Certificate) Empty() bool {
|
||||
// Hash returns a checksum of the certificate chain's DER-encoded bytes.
|
||||
func (cert Certificate) Hash() string { return cert.hash }
|
||||
|
||||
// NeedsRenewal returns true if the certificate is
|
||||
// expiring soon (according to cfg) or has expired.
|
||||
// NeedsRenewal returns true if the certificate is expiring
|
||||
// soon (according to ARI and/or cfg) or has expired.
|
||||
func (cert Certificate) NeedsRenewal(cfg *Config) bool {
|
||||
return currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio)
|
||||
return cfg.certNeedsRenewal(cert.Leaf, cert.ari, true)
|
||||
}
|
||||
|
||||
// certNeedsRenewal consults ACME Renewal Info (ARI) and certificate expiration to determine
|
||||
// whether the leaf certificate needs to be renewed yet. If true is returned, the certificate
|
||||
// should be renewed as soon as possible. The reasoning for a true return value is logged
|
||||
// unless emitLogs is false; this can be useful to suppress noisy logs in the case where you
|
||||
// first call this to determine if a cert in memory needs renewal, and then right after you
|
||||
// call it again to see if the cert in storage still needs renewal -- you probably don't want
|
||||
// to log the second time for checking the cert in storage which is mainly for synchronization.
|
||||
func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo, emitLogs bool) bool {
|
||||
expiration := expiresAt(leaf)
|
||||
|
||||
var logger *zap.Logger
|
||||
if emitLogs {
|
||||
logger = cfg.Logger.With(
|
||||
zap.Strings("subjects", leaf.DNSNames),
|
||||
zap.Time("expiration", expiration),
|
||||
zap.String("ari_cert_id", ari.UniqueIdentifier),
|
||||
zap.Timep("next_ari_update", ari.RetryAfter),
|
||||
zap.Duration("renew_check_interval", cfg.certCache.options.RenewCheckInterval),
|
||||
zap.Time("window_start", ari.SuggestedWindow.Start),
|
||||
zap.Time("window_end", ari.SuggestedWindow.End))
|
||||
} else {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
|
||||
// first check ARI: if it says it's time to renew, it's time to renew
|
||||
// (notice that we don't strictly require an ARI window to also exist; we presume
|
||||
// that if a time has been selected, a window does or did exist, even if it didn't
|
||||
// get stored/encoded for some reason - but also: this allows administrators to
|
||||
// manually or explicitly schedule a renewal time indepedently of ARI which could
|
||||
// be useful)
|
||||
selectedTime := ari.SelectedTime
|
||||
|
||||
// if, for some reason a random time in the window hasn't been selected yet, but an ARI
|
||||
// window does exist, we can always improvise one... even if this is called repeatedly,
|
||||
// a random time is a random time, whether you generate it once or more :D
|
||||
// (code borrowed from our acme package)
|
||||
if selectedTime.IsZero() &&
|
||||
(!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) {
|
||||
start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix()
|
||||
selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC()
|
||||
logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now",
|
||||
zap.Time("ephemeral_selected_time", selectedTime))
|
||||
}
|
||||
|
||||
// if a renewal time has been selected, start with that
|
||||
if !selectedTime.IsZero() {
|
||||
// ARI spec recommends an algorithm that renews after the randomly-selected
|
||||
// time OR just before it if the next waking time would be after it; this
|
||||
// cutoff can actually be before the start of the renewal window, but the spec
|
||||
// author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71
|
||||
cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval)
|
||||
if time.Now().After(cutoff) {
|
||||
logger.Info("certificate needs renewal based on ARI window",
|
||||
zap.Time("selected_time", selectedTime),
|
||||
zap.Time("renewal_cutoff", cutoff))
|
||||
return true
|
||||
}
|
||||
|
||||
// according to ARI, we are not ready to renew; however, we do not rely solely on
|
||||
// ARI calculations... what if there is a bug in our implementation, or in the
|
||||
// server's, or the stored metadata? for redundancy, give credence to the expiration
|
||||
// date; ignore ARI if we are past a "dangerously close" limit, to avoid any
|
||||
// possibility of a bug in ARI compromising a site's uptime: we should always always
|
||||
// always give heed to actual validity period
|
||||
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) {
|
||||
logger.Warn("certificate is in emergency renewal window; superceding ARI",
|
||||
zap.Duration("remaining", time.Until(expiration)),
|
||||
zap.Time("renewal_cutoff", cutoff))
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the normal check, in the absence of ARI, is to determine if we're near enough (or past)
|
||||
// the expiration date based on the configured remaining:lifetime ratio
|
||||
if currentlyInRenewalWindow(leaf.NotBefore, expiration, cfg.RenewalWindowRatio) {
|
||||
logger.Info("certificate is in configured renewal window based on expiration date",
|
||||
zap.Duration("remaining", time.Until(expiration)))
|
||||
return true
|
||||
}
|
||||
|
||||
// finally, if the certificate is expiring imminently, always attempt a renewal;
|
||||
// we check both a (very low) lifetime ratio and also a strict difference between
|
||||
// the time until expiration and the interval at which we run the standard maintenance
|
||||
// routine to check for renewals, to accommodate both exceptionally long and short
|
||||
// cert lifetimes
|
||||
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/50.0) ||
|
||||
time.Until(expiration) < cfg.certCache.options.RenewCheckInterval*5 {
|
||||
logger.Warn("certificate is in emergency renewal window; expiration imminent",
|
||||
zap.Duration("remaining", time.Until(expiration)))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Expired returns true if the certificate has expired.
|
||||
@ -85,10 +187,12 @@ func (cert Certificate) Expired() bool {
|
||||
return time.Now().After(expiresAt(cert.Leaf))
|
||||
}
|
||||
|
||||
// currentlyInRenewalWindow returns true if the current time is
|
||||
// within the renewal window, according to the given start/end
|
||||
// currentlyInRenewalWindow returns true if the current time is within
|
||||
// (or after) the renewal window, according to the given start/end
|
||||
// dates and the ratio of the renewal window. If true is returned,
|
||||
// the certificate being considered is due for renewal.
|
||||
// the certificate being considered is due for renewal. The ratio
|
||||
// is remaining:total time, i.e. 1/3 = 1/3 of lifetime remaining,
|
||||
// or 9/10 = 9/10 of time lifetime remaining.
|
||||
func currentlyInRenewalWindow(notBefore, notAfter time.Time, renewalWindowRatio float64) bool {
|
||||
if notAfter.IsZero() {
|
||||
return false
|
||||
@ -130,6 +234,7 @@ func expiresAt(cert *x509.Certificate) time.Time {
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheManagedCertificate(ctx context.Context, domain string) (Certificate, error) {
|
||||
domain = cfg.transformSubject(ctx, nil, domain)
|
||||
cert, err := cfg.loadManagedCertificate(ctx, domain)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
@ -153,16 +258,44 @@ func (cfg *Config) loadManagedCertificate(ctx context.Context, domain string) (C
|
||||
}
|
||||
cert.managed = true
|
||||
cert.issuerKey = certRes.issuerKey
|
||||
if ari, err := certRes.getARI(); err == nil && ari != nil {
|
||||
cert.ari = *ari
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// getARI unpacks ACME Renewal Information from the issuer data, if available.
|
||||
// It is only an error if there is invalid JSON.
|
||||
func (certRes CertificateResource) getARI() (*acme.RenewalInfo, error) {
|
||||
acmeData, err := certRes.getACMEData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return acmeData.RenewalInfo, nil
|
||||
}
|
||||
|
||||
// getACMEData returns the ACME certificate metadata from the IssuerData, but
|
||||
// note that a non-ACME-issued certificate may return an empty value and nil
|
||||
// since the JSON may still decode successfully but just not match any or all
|
||||
// of the fields. Remember that the IssuerKey is used to store and access the
|
||||
// cert files in the first place (it is part of the path) so in theory if you
|
||||
// load a CertificateResource from an ACME issuer it should work as expected.
|
||||
func (certRes CertificateResource) getACMEData() (acme.Certificate, error) {
|
||||
if len(certRes.IssuerData) == 0 {
|
||||
return acme.Certificate{}, nil
|
||||
}
|
||||
var acmeCert acme.Certificate
|
||||
err := json.Unmarshal(certRes.IssuerData, &acmeCert)
|
||||
return acmeCert, err
|
||||
}
|
||||
|
||||
// CacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
|
||||
// and keyFile, which must be in PEM format. It stores the certificate in
|
||||
// the in-memory cache and returns the hash, useful for removing from the cache.
|
||||
//
|
||||
// This method is safe for concurrent use.
|
||||
func (cfg *Config) CacheUnmanagedCertificatePEMFile(ctx context.Context, certFile, keyFile string, tags []string) (string, error) {
|
||||
cert, err := cfg.makeCertificateFromDiskWithOCSP(ctx, cfg.Storage, certFile, keyFile)
|
||||
cert, err := cfg.makeCertificateFromDiskWithOCSP(ctx, certFile, keyFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -185,6 +318,15 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(ctx context.Context, tlsCert tls
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if time.Now().After(cert.Leaf.NotAfter) {
|
||||
cfg.Logger.Warn("unmanaged certificate has expired",
|
||||
zap.Time("not_after", cert.Leaf.NotAfter),
|
||||
zap.Strings("sans", cert.Names))
|
||||
} else if time.Until(cert.Leaf.NotAfter) < 24*time.Hour {
|
||||
cfg.Logger.Warn("unmanaged certificate expires within 1 day",
|
||||
zap.Time("not_after", cert.Leaf.NotAfter),
|
||||
zap.Strings("sans", cert.Names))
|
||||
}
|
||||
err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil)
|
||||
if err != nil {
|
||||
cfg.Logger.Warn("stapling OCSP", zap.Error(err))
|
||||
@ -215,7 +357,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMBytes(ctx context.Context, certBy
|
||||
// certificate and key files. It fills out all the fields in
|
||||
// the certificate except for the Managed and OnDemand flags.
|
||||
// (It is up to the caller to set those.) It staples OCSP.
|
||||
func (cfg Config) makeCertificateFromDiskWithOCSP(ctx context.Context, storage Storage, certFile, keyFile string) (Certificate, error) {
|
||||
func (cfg Config) makeCertificateFromDiskWithOCSP(ctx context.Context, certFile, keyFile string) (Certificate, error) {
|
||||
certPEMBlock, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
@ -319,21 +461,22 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// managedCertInStorageExpiresSoon returns true if cert (being a
|
||||
// managed certificate) is expiring within RenewDurationBefore.
|
||||
// It returns false if there was an error checking the expiration
|
||||
// of the certificate as found in storage, or if the certificate
|
||||
// in storage is NOT expiring soon. A certificate that is expiring
|
||||
// managedCertInStorageNeedsRenewal returns true if cert (being a
|
||||
// managed certificate) is expiring soon (according to cfg) or if
|
||||
// ACME Renewal Information (ARI) is available and says that it is
|
||||
// time to renew (it uses existing ARI; it does not update it).
|
||||
// It returns false if there was an error, the cert is not expiring
|
||||
// soon, and ARI window is still future. A certificate that is expiring
|
||||
// soon in our cache but is not expiring soon in storage probably
|
||||
// means that another instance renewed the certificate in the
|
||||
// meantime, and it would be a good idea to simply load the cert
|
||||
// into our cache rather than repeating the renewal process again.
|
||||
func (cfg *Config) managedCertInStorageExpiresSoon(ctx context.Context, cert Certificate) (bool, error) {
|
||||
func (cfg *Config) managedCertInStorageNeedsRenewal(ctx context.Context, cert Certificate) (bool, error) {
|
||||
certRes, err := cfg.loadCertResourceAnyIssuer(ctx, cert.Names[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, needsRenew := cfg.managedCertNeedsRenewal(certRes)
|
||||
_, _, needsRenew := cfg.managedCertNeedsRenewal(certRes, false)
|
||||
return needsRenew, nil
|
||||
}
|
||||
|
||||
@ -376,8 +519,8 @@ func SubjectQualifiesForCert(subj string) bool {
|
||||
|
||||
// SubjectQualifiesForPublicCert returns true if the subject
|
||||
// name appears eligible for automagic TLS with a public
|
||||
// CA such as Let's Encrypt. For example: localhost and IP
|
||||
// addresses are not eligible because we cannot obtain certs
|
||||
// CA such as Let's Encrypt. For example: internal IP addresses
|
||||
// and localhost are not eligible because we cannot obtain certs
|
||||
// for those names with a public CA. Wildcard names are
|
||||
// allowed, as long as they conform to CABF requirements (only
|
||||
// one wildcard label, and it must be the left-most label).
|
||||
@ -385,13 +528,9 @@ func SubjectQualifiesForPublicCert(subj string) bool {
|
||||
// must at least qualify for a certificate
|
||||
return SubjectQualifiesForCert(subj) &&
|
||||
|
||||
// localhost, .localhost TLD, and .local TLD are ineligible
|
||||
// loopback hosts and internal IPs are ineligible
|
||||
!SubjectIsInternal(subj) &&
|
||||
|
||||
// cannot be an IP address (as of yet), see
|
||||
// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
|
||||
!SubjectIsIP(subj) &&
|
||||
|
||||
// only one wildcard label allowed, and it must be left-most, with 3+ labels
|
||||
(!strings.Contains(subj, "*") ||
|
||||
(strings.Count(subj, "*") == 1 &&
|
||||
@ -406,12 +545,55 @@ func SubjectIsIP(subj string) bool {
|
||||
}
|
||||
|
||||
// SubjectIsInternal returns true if subj is an internal-facing
|
||||
// hostname or address.
|
||||
// hostname or address, including localhost/loopback hosts.
|
||||
// Ports are ignored, if present.
|
||||
func SubjectIsInternal(subj string) bool {
|
||||
subj = strings.ToLower(strings.TrimSuffix(hostOnly(subj), "."))
|
||||
return subj == "localhost" ||
|
||||
strings.HasSuffix(subj, ".localhost") ||
|
||||
strings.HasSuffix(subj, ".local") ||
|
||||
strings.HasSuffix(subj, ".home.arpa")
|
||||
strings.HasSuffix(subj, ".home.arpa") ||
|
||||
isInternalIP(subj)
|
||||
}
|
||||
|
||||
// isInternalIP returns true if the IP of addr
|
||||
// belongs to a private network IP range. addr
|
||||
// must only be an IP or an IP:port combination.
|
||||
func isInternalIP(addr string) bool {
|
||||
privateNetworks := []string{
|
||||
"127.0.0.0/8", // IPv4 loopback
|
||||
"0.0.0.0/16",
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
"169.254.0.0/16", // RFC3927 link-local
|
||||
"::1/7", // IPv6 loopback
|
||||
"fe80::/10", // IPv6 link-local
|
||||
"fc00::/7", // IPv6 unique local addr
|
||||
}
|
||||
host := hostOnly(addr)
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
for _, privateNetwork := range privateNetworks {
|
||||
_, ipnet, _ := net.ParseCIDR(privateNetwork)
|
||||
if ipnet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hostOnly returns only the host portion of hostport.
|
||||
// If there is no port or if there is an error splitting
|
||||
// the port off, the whole input string is returned.
|
||||
func hostOnly(hostport string) string {
|
||||
host, _, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport // OK; probably had no port to begin with
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// MatchWildcard returns true if subject (a candidate DNS name)
|
||||
|
||||
59
vendor/github.com/caddyserver/certmagic/certmagic.go
generated
vendored
59
vendor/github.com/caddyserver/certmagic/certmagic.go
generated
vendored
@ -39,6 +39,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -302,52 +303,6 @@ type OnDemandConfig struct {
|
||||
hostAllowlist map[string]struct{}
|
||||
}
|
||||
|
||||
// isLoopback returns true if the hostname of addr looks
|
||||
// explicitly like a common local hostname. addr must only
|
||||
// be a host or a host:port combination.
|
||||
func isLoopback(addr string) bool {
|
||||
host := hostOnly(addr)
|
||||
return host == "localhost" ||
|
||||
strings.Trim(host, "[]") == "::1" ||
|
||||
strings.HasPrefix(host, "127.")
|
||||
}
|
||||
|
||||
// isInternal returns true if the IP of addr
|
||||
// belongs to a private network IP range. addr
|
||||
// must only be an IP or an IP:port combination.
|
||||
// Loopback addresses are considered false.
|
||||
func isInternal(addr string) bool {
|
||||
privateNetworks := []string{
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7",
|
||||
}
|
||||
host := hostOnly(addr)
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
for _, privateNetwork := range privateNetworks {
|
||||
_, ipnet, _ := net.ParseCIDR(privateNetwork)
|
||||
if ipnet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hostOnly returns only the host portion of hostport.
|
||||
// If there is no port or if there is an error splitting
|
||||
// the port off, the whole input string is returned.
|
||||
func hostOnly(hostport string) string {
|
||||
host, _, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport // OK; probably had no port to begin with
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// PreChecker is an interface that can be optionally implemented by
|
||||
// Issuers. Pre-checks are performed before each call (or batch of
|
||||
// identical calls) to Issue(), giving the issuer the option to ensure
|
||||
@ -394,7 +349,12 @@ type Revoker interface {
|
||||
type Manager interface {
|
||||
// GetCertificate returns the certificate to use to complete the handshake.
|
||||
// Since this is called during every TLS handshake, it must be very fast and not block.
|
||||
// Returning (nil, nil) is valid and is simply treated as a no-op.
|
||||
// Returning any non-nil value indicates that this Manager manages a certificate
|
||||
// for the described handshake. Returning (nil, nil) is valid and is simply treated as
|
||||
// a no-op Return (nil, nil) when the Manager has no certificate for this handshake.
|
||||
// Return an error or a certificate only if the Manager is supposed to get a certificate
|
||||
// for this handshake. Returning (nil, nil) other Managers or Issuers to try to get
|
||||
// a certificate for the handshake.
|
||||
GetCertificate(context.Context, *tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
}
|
||||
|
||||
@ -429,7 +389,8 @@ type IssuedCertificate struct {
|
||||
Certificate []byte
|
||||
|
||||
// Any extra information to serialize alongside the
|
||||
// certificate in storage.
|
||||
// certificate in storage. It MUST be serializable
|
||||
// as JSON in order to be preserved.
|
||||
Metadata any
|
||||
}
|
||||
|
||||
@ -450,7 +411,7 @@ type CertificateResource struct {
|
||||
|
||||
// Any extra information associated with the certificate,
|
||||
// usually provided by the issuer implementation.
|
||||
IssuerData any `json:"issuer_data,omitempty"`
|
||||
IssuerData json.RawMessage `json:"issuer_data,omitempty"`
|
||||
|
||||
// The unique string identifying the issuer of the
|
||||
// certificate; internally useful for storage access.
|
||||
|
||||
152
vendor/github.com/caddyserver/certmagic/config.go
generated
vendored
152
vendor/github.com/caddyserver/certmagic/config.go
generated
vendored
@ -24,17 +24,19 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
"golang.org/x/net/idna"
|
||||
@ -50,6 +52,7 @@ type Config struct {
|
||||
// it should be renewed; for most certificates, the
|
||||
// global default is good, but for extremely short-
|
||||
// lived certs, you may want to raise this to ~0.5.
|
||||
// Ratio is remaining:total lifetime.
|
||||
RenewalWindowRatio float64
|
||||
|
||||
// An optional event callback clients can set
|
||||
@ -135,9 +138,17 @@ type Config struct {
|
||||
// storage is properly configured and has sufficient
|
||||
// space, you can disable this check to reduce I/O
|
||||
// if that is expensive for you.
|
||||
// EXPERIMENTAL: Option subject to change or removal.
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
DisableStorageCheck bool
|
||||
|
||||
// SubjectTransformer is a hook that can transform the
|
||||
// subject (SAN) of a certificate being loaded or issued.
|
||||
// For example, a common use case is to replace the
|
||||
// left-most label with an asterisk (*) to become a
|
||||
// wildcard certificate.
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
SubjectTransformer func(ctx context.Context, domain string) string
|
||||
|
||||
// Set a logger to enable logging. If not set,
|
||||
// a default logger will be created.
|
||||
Logger *zap.Logger
|
||||
@ -436,6 +447,15 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure ARI is updated before we check whether the cert needs renewing
|
||||
// (we ignore the second return value because we already check if needs renewing anyway)
|
||||
if cert.ari.NeedsRefresh() {
|
||||
cert, _, err = cfg.updateARI(ctx, cert, cfg.Logger)
|
||||
if err != nil {
|
||||
cfg.Logger.Error("updating ARI upon managing", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, simply renew the certificate if needed
|
||||
if cert.NeedsRenewal(cfg) {
|
||||
var err error
|
||||
@ -484,6 +504,10 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
return fmt.Errorf("no issuers configured; impossible to obtain or check for existing certificate in storage")
|
||||
}
|
||||
|
||||
log := cfg.Logger.Named("obtain")
|
||||
|
||||
name = cfg.transformSubject(ctx, log, name)
|
||||
|
||||
// if storage has all resources for this certificate, obtain is a no-op
|
||||
if cfg.storageHasCertResourcesAnyIssuer(ctx, name) {
|
||||
return nil
|
||||
@ -496,8 +520,6 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
|
||||
}
|
||||
|
||||
log := cfg.Logger.Named("obtain")
|
||||
|
||||
log.Info("acquiring lock", zap.String("identifier", name))
|
||||
|
||||
// ensure idempotency of the obtain operation for this name
|
||||
@ -561,7 +583,7 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
}
|
||||
}
|
||||
|
||||
csr, err := cfg.generateCSR(privKey, []string{name})
|
||||
csr, err := cfg.generateCSR(privKey, []string{name}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -583,7 +605,19 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
}
|
||||
}
|
||||
|
||||
issuedCert, err = issuer.Issue(ctx, csr)
|
||||
// TODO: ZeroSSL's API currently requires CommonName to be set, and requires it be
|
||||
// distinct from SANs. If this was a cert it would violate the BRs, but their certs
|
||||
// are compliant, so their CSR requirements just needlessly add friction, complexity,
|
||||
// and inefficiency for clients. CommonName has been deprecated for 25+ years.
|
||||
useCSR := csr
|
||||
if issuer.IssuerKey() == zerosslIssuerKey {
|
||||
useCSR, err = cfg.generateCSR(privKey, []string{name}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
issuedCert, err = issuer.Issue(ctx, useCSR)
|
||||
if err == nil {
|
||||
issuerUsed = issuer
|
||||
break
|
||||
@ -615,11 +649,15 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
issuerKey := issuerUsed.IssuerKey()
|
||||
|
||||
// success - immediately save the certificate resource
|
||||
metaJSON, err := json.Marshal(issuedCert.Metadata)
|
||||
if err != nil {
|
||||
log.Error("unable to encode certificate metadata", zap.Error(err))
|
||||
}
|
||||
certRes := CertificateResource{
|
||||
SANs: namesFromCSR(csr),
|
||||
CertificatePEM: issuedCert.Certificate,
|
||||
PrivateKeyPEM: privKeyPEM,
|
||||
IssuerData: issuedCert.Metadata,
|
||||
IssuerData: metaJSON,
|
||||
issuerKey: issuerUsed.IssuerKey(),
|
||||
}
|
||||
err = cfg.saveCertResource(ctx, issuerUsed, certRes)
|
||||
@ -627,7 +665,9 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
|
||||
}
|
||||
|
||||
log.Info("certificate obtained successfully", zap.String("identifier", name))
|
||||
log.Info("certificate obtained successfully",
|
||||
zap.String("identifier", name),
|
||||
zap.String("issuer", issuerUsed.IssuerKey()))
|
||||
|
||||
certKey := certRes.NamesKey()
|
||||
|
||||
@ -639,6 +679,10 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
|
||||
"private_key_path": StorageKeys.SitePrivateKey(issuerKey, certKey),
|
||||
"certificate_path": StorageKeys.SiteCert(issuerKey, certKey),
|
||||
"metadata_path": StorageKeys.SiteMeta(issuerKey, certKey),
|
||||
"csr_pem": pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr.Raw,
|
||||
}),
|
||||
})
|
||||
|
||||
return nil
|
||||
@ -723,6 +767,10 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
return fmt.Errorf("no issuers configured; impossible to renew or check existing certificate in storage")
|
||||
}
|
||||
|
||||
log := cfg.Logger.Named("renew")
|
||||
|
||||
name = cfg.transformSubject(ctx, log, name)
|
||||
|
||||
// ensure storage is writeable and readable
|
||||
// TODO: this is not necessary every time; should only perform check once every so often for each storage, which may require some global state...
|
||||
err := cfg.checkStorage(ctx)
|
||||
@ -730,8 +778,6 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
|
||||
}
|
||||
|
||||
log := cfg.Logger.Named("renew")
|
||||
|
||||
log.Info("acquiring lock", zap.String("identifier", name))
|
||||
|
||||
// ensure idempotency of the renew operation for this name
|
||||
@ -760,7 +806,7 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
}
|
||||
|
||||
// check if renew is still needed - might have been renewed while waiting for lock
|
||||
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
|
||||
timeLeft, leaf, needsRenew := cfg.managedCertNeedsRenewal(certRes, false)
|
||||
if !needsRenew {
|
||||
if force {
|
||||
log.Info("certificate does not need to be renewed, but renewal is being forced",
|
||||
@ -807,7 +853,7 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
}
|
||||
}
|
||||
|
||||
csr, err := cfg.generateCSR(privateKey, []string{name})
|
||||
csr, err := cfg.generateCSR(privateKey, []string{name}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -817,6 +863,18 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
var issuerUsed Issuer
|
||||
var issuerKeys []string
|
||||
for _, issuer := range cfg.Issuers {
|
||||
// TODO: ZeroSSL's API currently requires CommonName to be set, and requires it be
|
||||
// distinct from SANs. If this was a cert it would violate the BRs, but their certs
|
||||
// are compliant, so their CSR requirements just needlessly add friction, complexity,
|
||||
// and inefficiency for clients. CommonName has been deprecated for 25+ years.
|
||||
useCSR := csr
|
||||
if _, ok := issuer.(*ZeroSSLIssuer); ok {
|
||||
useCSR, err = cfg.generateCSR(privateKey, []string{name}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
issuerKeys = append(issuerKeys, issuer.IssuerKey())
|
||||
if prechecker, ok := issuer.(PreChecker); ok {
|
||||
err = prechecker.PreCheck(ctx, []string{name}, interactive)
|
||||
@ -825,7 +883,19 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
}
|
||||
}
|
||||
|
||||
issuedCert, err = issuer.Issue(ctx, csr)
|
||||
// if we're renewing with the same ACME CA as before, have the ACME
|
||||
// client tell the server we are replacing a certificate (but doing
|
||||
// this on the wrong CA, or when the CA doesn't recognize the certID,
|
||||
// can fail the order)
|
||||
if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" {
|
||||
if acmeIss, ok := issuer.(*ACMEIssuer); ok {
|
||||
if acmeIss.CA == acmeData.CA {
|
||||
ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issuedCert, err = issuer.Issue(ctx, useCSR)
|
||||
if err == nil {
|
||||
issuerUsed = issuer
|
||||
break
|
||||
@ -858,11 +928,15 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
issuerKey := issuerUsed.IssuerKey()
|
||||
|
||||
// success - immediately save the renewed certificate resource
|
||||
metaJSON, err := json.Marshal(issuedCert.Metadata)
|
||||
if err != nil {
|
||||
log.Error("unable to encode certificate metadata", zap.Error(err))
|
||||
}
|
||||
newCertRes := CertificateResource{
|
||||
SANs: namesFromCSR(csr),
|
||||
CertificatePEM: issuedCert.Certificate,
|
||||
PrivateKeyPEM: certRes.PrivateKeyPEM,
|
||||
IssuerData: issuedCert.Metadata,
|
||||
IssuerData: metaJSON,
|
||||
issuerKey: issuerKey,
|
||||
}
|
||||
err = cfg.saveCertResource(ctx, issuerUsed, newCertRes)
|
||||
@ -870,7 +944,9 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
|
||||
}
|
||||
|
||||
log.Info("certificate renewed successfully", zap.String("identifier", name))
|
||||
log.Info("certificate renewed successfully",
|
||||
zap.String("identifier", name),
|
||||
zap.String("issuer", issuerKey))
|
||||
|
||||
certKey := newCertRes.NamesKey()
|
||||
|
||||
@ -883,6 +959,10 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
"private_key_path": StorageKeys.SitePrivateKey(issuerKey, certKey),
|
||||
"certificate_path": StorageKeys.SiteCert(issuerKey, certKey),
|
||||
"metadata_path": StorageKeys.SiteMeta(issuerKey, certKey),
|
||||
"csr_pem": pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr.Raw,
|
||||
}),
|
||||
})
|
||||
|
||||
return nil
|
||||
@ -897,10 +977,16 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
|
||||
return err
|
||||
}
|
||||
|
||||
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x509.CertificateRequest, error) {
|
||||
// generateCSR generates a CSR for the given SANs. If useCN is true, CommonName will get the first SAN (TODO: this is only a temporary hack for ZeroSSL API support).
|
||||
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string, useCN bool) (*x509.CertificateRequest, error) {
|
||||
csrTemplate := new(x509.CertificateRequest)
|
||||
|
||||
for _, name := range sans {
|
||||
// TODO: This is a temporary hack to support ZeroSSL API...
|
||||
if useCN && csrTemplate.Subject.CommonName == "" && len(name) <= 64 {
|
||||
csrTemplate.Subject.CommonName = name
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(name); ip != nil {
|
||||
csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip)
|
||||
} else if strings.Contains(name, "@") {
|
||||
@ -1052,6 +1138,19 @@ func (cfg *Config) getChallengeInfo(ctx context.Context, identifier string) (Cha
|
||||
return Challenge{Challenge: chalInfo}, true, nil
|
||||
}
|
||||
|
||||
func (cfg *Config) transformSubject(ctx context.Context, logger *zap.Logger, name string) string {
|
||||
if cfg.SubjectTransformer == nil {
|
||||
return name
|
||||
}
|
||||
transformedName := cfg.SubjectTransformer(ctx, name)
|
||||
if logger != nil && transformedName != name {
|
||||
logger.Debug("transformed subject name",
|
||||
zap.String("original", name),
|
||||
zap.String("transformed", transformedName))
|
||||
}
|
||||
return transformedName
|
||||
}
|
||||
|
||||
// checkStorage tests the storage by writing random bytes
|
||||
// to a random key, and then loading those bytes and
|
||||
// comparing the loaded value. If this fails, the provided
|
||||
@ -1137,14 +1236,19 @@ func (cfg *Config) lockKey(op, domainName string) string {
|
||||
|
||||
// managedCertNeedsRenewal returns true if certRes is expiring soon or already expired,
|
||||
// or if the process of decoding the cert and checking its expiration returned an error.
|
||||
func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Duration, bool) {
|
||||
// If there wasn't an error, the leaf cert is also returned, so it can be reused if
|
||||
// necessary, since we are parsing the PEM bundle anyway.
|
||||
func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource, emitLogs bool) (time.Duration, *x509.Certificate, bool) {
|
||||
certChain, err := parseCertsFromPEMBundle(certRes.CertificatePEM)
|
||||
if err != nil {
|
||||
return 0, true
|
||||
if err != nil || len(certChain) == 0 {
|
||||
return 0, nil, true
|
||||
}
|
||||
var ari acme.RenewalInfo
|
||||
if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil {
|
||||
ari = *ariPtr
|
||||
}
|
||||
remaining := time.Until(expiresAt(certChain[0]))
|
||||
needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, expiresAt(certChain[0]), cfg.RenewalWindowRatio)
|
||||
return remaining, needsRenew
|
||||
return remaining, certChain[0], cfg.certNeedsRenewal(certChain[0], ari, emitLogs)
|
||||
}
|
||||
|
||||
func (cfg *Config) emit(ctx context.Context, eventName string, data map[string]any) error {
|
||||
@ -1173,6 +1277,10 @@ type OCSPConfig struct {
|
||||
// embedded in certificates. Mapping to an empty
|
||||
// URL will disable OCSP from that responder.
|
||||
ResponderOverrides map[string]string
|
||||
|
||||
// Optionally specify a function that can return the URL
|
||||
// for an HTTP proxy to use for OCSP-related HTTP requests.
|
||||
HTTPProxy func(*http.Request) (*url.URL, error)
|
||||
}
|
||||
|
||||
// certIssueLockOp is the name of the operation used
|
||||
|
||||
5
vendor/github.com/caddyserver/certmagic/crypto.go
generated
vendored
5
vendor/github.com/caddyserver/certmagic/crypto.go
generated
vendored
@ -280,6 +280,11 @@ func hashCertificateChain(certChain [][]byte) string {
|
||||
|
||||
func namesFromCSR(csr *x509.CertificateRequest) []string {
|
||||
var nameSet []string
|
||||
// TODO: CommonName should not be used (it has been deprecated for 25+ years,
|
||||
// but ZeroSSL CA still requires it to be filled out and not overlap SANs...)
|
||||
if csr.Subject.CommonName != "" {
|
||||
nameSet = append(nameSet, csr.Subject.CommonName)
|
||||
}
|
||||
nameSet = append(nameSet, csr.DNSNames...)
|
||||
nameSet = append(nameSet, csr.EmailAddresses...)
|
||||
for _, v := range csr.IPAddresses {
|
||||
|
||||
99
vendor/github.com/caddyserver/certmagic/dnsutil.go
generated
vendored
99
vendor/github.com/caddyserver/certmagic/dnsutil.go
generated
vendored
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Code in this file adapted from go-acme/lego, July 2020:
|
||||
@ -19,19 +20,21 @@ import (
|
||||
|
||||
// findZoneByFQDN determines the zone apex for the given fqdn by recursing
|
||||
// up the domain labels until the nameserver returns a SOA record in the
|
||||
// answer section.
|
||||
func findZoneByFQDN(fqdn string, nameservers []string) (string, error) {
|
||||
// answer section. The logger must be non-nil.
|
||||
func findZoneByFQDN(logger *zap.Logger, fqdn string, nameservers []string) (string, error) {
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
soa, err := lookupSoaByFqdn(logger, fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return soa.zone, nil
|
||||
}
|
||||
|
||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
func lookupSoaByFqdn(logger *zap.Logger, fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
logger = logger.Named("soa_lookup")
|
||||
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
@ -41,10 +44,11 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
|
||||
|
||||
// prefer cached version if fresh
|
||||
if ent := fqdnSOACache[fqdn]; ent != nil && !ent.isExpired() {
|
||||
logger.Debug("using cached SOA result", zap.String("entry", ent.zone))
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||
ent, err := fetchSoaByFqdn(logger, fqdn, nameservers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -62,7 +66,7 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
func fetchSoaByFqdn(logger *zap.Logger, fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
var err error
|
||||
var in *dns.Msg
|
||||
|
||||
@ -77,6 +81,7 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
if in == nil {
|
||||
continue
|
||||
}
|
||||
logger.Debug("fetched SOA", zap.String("msg", in.String()))
|
||||
|
||||
switch in.Rcode {
|
||||
case dns.RcodeSuccess:
|
||||
@ -210,36 +215,46 @@ func populateNameserverPorts(servers []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||
func checkDNSPropagation(fqdn, value string, resolvers []string) (bool, error) {
|
||||
// checkDNSPropagation checks if the expected record has been propagated to all authoritative nameservers.
|
||||
func checkDNSPropagation(logger *zap.Logger, fqdn string, recType uint16, expectedValue string, checkAuthoritativeServers bool, resolvers []string) (bool, error) {
|
||||
logger = logger.Named("propagation")
|
||||
|
||||
if !strings.HasSuffix(fqdn, ".") {
|
||||
fqdn += "."
|
||||
}
|
||||
|
||||
// Initial attempt to resolve at the recursive NS
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, resolvers, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// Initial attempt to resolve at the recursive NS - but do not actually
|
||||
// dereference (follow) a CNAME record if we are targeting a CNAME record
|
||||
// itself
|
||||
if recType != dns.TypeCNAME {
|
||||
r, err := dnsQuery(fqdn, recType, resolvers, true)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("CNAME dns query: %v", err)
|
||||
}
|
||||
if r.Rcode == dns.RcodeSuccess {
|
||||
fqdn = updateDomainWithCName(r, fqdn)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Rcode == dns.RcodeSuccess {
|
||||
fqdn = updateDomainWithCName(r, fqdn)
|
||||
if checkAuthoritativeServers {
|
||||
authoritativeServers, err := lookupNameservers(logger, fqdn, resolvers)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("looking up authoritative nameservers: %v", err)
|
||||
}
|
||||
populateNameserverPorts(authoritativeServers)
|
||||
resolvers = authoritativeServers
|
||||
}
|
||||
logger.Debug("checking authoritative nameservers", zap.Strings("resolvers", resolvers))
|
||||
|
||||
authoritativeNss, err := lookupNameservers(fqdn, resolvers)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||
return checkAuthoritativeNss(fqdn, recType, expectedValue, resolvers)
|
||||
}
|
||||
|
||||
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
|
||||
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
||||
// checkAuthoritativeNss queries each of the given nameservers for the expected record.
|
||||
func checkAuthoritativeNss(fqdn string, recType uint16, expectedValue string, nameservers []string) (bool, error) {
|
||||
for _, ns := range nameservers {
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, true)
|
||||
r, err := dnsQuery(fqdn, recType, []string{ns}, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("querying authoritative nameservers: %v", err)
|
||||
}
|
||||
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
@ -252,37 +267,43 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro
|
||||
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for _, rr := range r.Answer {
|
||||
if txt, ok := rr.(*dns.TXT); ok {
|
||||
record := strings.Join(txt.Txt, "")
|
||||
if record == value {
|
||||
found = true
|
||||
break
|
||||
switch recType {
|
||||
case dns.TypeTXT:
|
||||
if txt, ok := rr.(*dns.TXT); ok {
|
||||
record := strings.Join(txt.Txt, "")
|
||||
if record == expectedValue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
case dns.TypeCNAME:
|
||||
if cname, ok := rr.(*dns.CNAME); ok {
|
||||
// TODO: whether a DNS provider assumes a trailing dot or not varies, and we may have to standardize this in libdns packages
|
||||
if strings.TrimSuffix(cname.Target, ".") == strings.TrimSuffix(expectedValue, ".") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported record type: %d", recType)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
||||
func lookupNameservers(fqdn string, resolvers []string) ([]string, error) {
|
||||
func lookupNameservers(logger *zap.Logger, fqdn string, resolvers []string) ([]string, error) {
|
||||
var authoritativeNss []string
|
||||
|
||||
zone, err := findZoneByFQDN(fqdn, resolvers)
|
||||
zone, err := findZoneByFQDN(logger, fqdn, resolvers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the zone: %w", err)
|
||||
return nil, fmt.Errorf("could not determine the zone for '%s': %w", fqdn, err)
|
||||
}
|
||||
|
||||
r, err := dnsQuery(zone, dns.TypeNS, resolvers, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("querying NS resolver for zone '%s' recursively: %v", zone, err)
|
||||
}
|
||||
|
||||
for _, rr := range r.Answer {
|
||||
|
||||
2
vendor/github.com/caddyserver/certmagic/filestorage.go
generated
vendored
2
vendor/github.com/caddyserver/certmagic/filestorage.go
generated
vendored
@ -92,7 +92,7 @@ func (s *FileStorage) Load(_ context.Context, key string) ([]byte, error) {
|
||||
|
||||
// Delete deletes the value at key.
|
||||
func (s *FileStorage) Delete(_ context.Context, key string) error {
|
||||
return os.Remove(s.Filename(key))
|
||||
return os.RemoveAll(s.Filename(key))
|
||||
}
|
||||
|
||||
// List returns all keys that match prefix.
|
||||
|
||||
116
vendor/github.com/caddyserver/certmagic/handshake.go
generated
vendored
116
vendor/github.com/caddyserver/certmagic/handshake.go
generated
vendored
@ -25,7 +25,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
@ -65,24 +65,27 @@ func (cfg *Config) GetCertificateWithContext(ctx context.Context, clientHello *t
|
||||
ctx = context.WithValue(ctx, ClientHelloInfoCtxKey, clientHello)
|
||||
|
||||
// special case: serve up the certificate for a TLS-ALPN ACME challenge
|
||||
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
|
||||
for _, proto := range clientHello.SupportedProtos {
|
||||
if proto == acmez.ACMETLS1Protocol {
|
||||
challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello)
|
||||
if err != nil {
|
||||
cfg.Logger.Error("tls-alpn challenge",
|
||||
zap.String("remote_addr", clientHello.Conn.RemoteAddr().String()),
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
cfg.Logger.Info("served key authentication certificate",
|
||||
// (https://www.rfc-editor.org/rfc/rfc8737.html)
|
||||
// "The ACME server MUST provide an ALPN extension with the single protocol
|
||||
// name "acme-tls/1" and an SNI extension containing only the domain name
|
||||
// being validated during the TLS handshake."
|
||||
if clientHello.ServerName != "" &&
|
||||
len(clientHello.SupportedProtos) == 1 &&
|
||||
clientHello.SupportedProtos[0] == acmez.ACMETLS1Protocol {
|
||||
challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello)
|
||||
if err != nil {
|
||||
cfg.Logger.Error("tls-alpn challenge",
|
||||
zap.String("remote_addr", clientHello.Conn.RemoteAddr().String()),
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.String("challenge", "tls-alpn-01"),
|
||||
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
|
||||
zap.Bool("distributed", distributed))
|
||||
return challengeCert, nil
|
||||
zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
cfg.Logger.Info("served key authentication certificate",
|
||||
zap.String("server_name", clientHello.ServerName),
|
||||
zap.String("challenge", "tls-alpn-01"),
|
||||
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
|
||||
zap.Bool("distributed", distributed))
|
||||
return challengeCert, nil
|
||||
}
|
||||
|
||||
// get the certificate and serve it up
|
||||
@ -120,7 +123,7 @@ func (cfg *Config) getCertificateFromCache(hello *tls.ClientHelloInfo) (cert Cer
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to a "default" certificate, if specified
|
||||
// use a "default" certificate by name, if specified
|
||||
if cfg.DefaultServerName != "" {
|
||||
normDefault := normalizedName(cfg.DefaultServerName)
|
||||
cert, defaulted = cfg.selectCert(hello, normDefault)
|
||||
@ -316,13 +319,6 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
|
||||
}()
|
||||
}
|
||||
|
||||
// Make sure a certificate is allowed for the given name. If not, it doesn't
|
||||
// make sense to try loading one from storage (issue #185), getting it from a
|
||||
// certificate manager, or obtaining one from an issuer.
|
||||
if err := cfg.checkIfCertShouldBeObtained(ctx, name, false); err != nil {
|
||||
return Certificate{}, fmt.Errorf("certificate is not allowed for server name %s: %w", name, err)
|
||||
}
|
||||
|
||||
// If an external Manager is configured, try to get it from them.
|
||||
// Only continue to use our own logic if it returns empty+nil.
|
||||
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, logger)
|
||||
@ -333,6 +329,12 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
|
||||
return externalCert, nil
|
||||
}
|
||||
|
||||
// Make sure a certificate is allowed for the given name. If not, it doesn't make sense
|
||||
// to try loading one from storage (issue #185) or obtaining one from an issuer.
|
||||
if err := cfg.checkIfCertShouldBeObtained(ctx, name, false); err != nil {
|
||||
return Certificate{}, fmt.Errorf("certificate is not allowed for server name %s: %w", name, err)
|
||||
}
|
||||
|
||||
// We might be able to load or obtain a needed certificate. Load from
|
||||
// storage if OnDemand is enabled, or if there is the possibility that
|
||||
// a statically-managed cert was evicted from a full cache.
|
||||
@ -547,11 +549,11 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
|
||||
log := cfg.Logger.Named("on_demand")
|
||||
logger := cfg.Logger.Named("on_demand")
|
||||
|
||||
// Check OCSP staple validity
|
||||
if cert.ocsp != nil && !freshOCSP(cert.ocsp) {
|
||||
log.Debug("OCSP response needs refreshing",
|
||||
logger.Debug("OCSP response needs refreshing",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Int("ocsp_status", cert.ocsp.Status),
|
||||
zap.Time("this_update", cert.ocsp.ThisUpdate),
|
||||
@ -561,12 +563,12 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
|
||||
if err != nil {
|
||||
// An error with OCSP stapling is not the end of the world, and in fact, is
|
||||
// quite common considering not all certs have issuer URLs that support it.
|
||||
log.Warn("stapling OCSP",
|
||||
logger.Warn("stapling OCSP",
|
||||
zap.String("server_name", hello.ServerName),
|
||||
zap.Strings("sans", cert.Names),
|
||||
zap.Error(err))
|
||||
} else {
|
||||
log.Debug("successfully stapled new OCSP response",
|
||||
logger.Debug("successfully stapled new OCSP response",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Int("ocsp_status", cert.ocsp.Status),
|
||||
zap.Time("this_update", cert.ocsp.ThisUpdate),
|
||||
@ -579,10 +581,20 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
|
||||
cfg.certCache.mu.Unlock()
|
||||
}
|
||||
|
||||
// Check ARI status
|
||||
if cert.ari.NeedsRefresh() {
|
||||
// we ignore the second return value here because we go on to check renewal status below regardless
|
||||
var err error
|
||||
cert, _, err = cfg.updateARI(ctx, cert, logger)
|
||||
if err != nil {
|
||||
logger.Error("updated ARI", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// We attempt to replace any certificates that were revoked.
|
||||
// Crucially, this happens OUTSIDE a lock on the certCache.
|
||||
if certShouldBeForceRenewed(cert) {
|
||||
log.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
|
||||
logger.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Int("ocsp_status", cert.ocsp.Status),
|
||||
zap.Time("revoked_at", cert.ocsp.RevokedAt),
|
||||
@ -592,14 +604,13 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
|
||||
}
|
||||
|
||||
// Check cert expiration
|
||||
if currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio) {
|
||||
if cfg.certNeedsRenewal(cert.Leaf, cert.ari, true) {
|
||||
// Check if the certificate still exists on disk. If not, we need to obtain a new one.
|
||||
// This can happen if the certificate was cleaned up by the storage cleaner, but still
|
||||
// remains in the in-memory cache.
|
||||
if !cfg.storageHasCertResourcesAnyIssuer(ctx, cert.Names[0]) {
|
||||
log.Debug("certificate not found on disk; obtaining new certificate",
|
||||
logger.Debug("certificate not found on disk; obtaining new certificate",
|
||||
zap.Strings("identifiers", cert.Names))
|
||||
|
||||
return cfg.obtainOnDemandCertificate(ctx, hello)
|
||||
}
|
||||
// Otherwise, renew the certificate.
|
||||
@ -621,7 +632,7 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
|
||||
//
|
||||
// This function is safe for use by multiple concurrent goroutines.
|
||||
func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) {
|
||||
log := logWithRemote(cfg.Logger.Named("on_demand"), hello)
|
||||
logger := logWithRemote(cfg.Logger.Named("on_demand"), hello)
|
||||
|
||||
name := cfg.getNameFromClientHello(hello)
|
||||
timeLeft := time.Until(expiresAt(currentCert.Leaf))
|
||||
@ -638,7 +649,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
|
||||
// renewing it, so we might as well serve what we have without blocking, UNLESS
|
||||
// we're forcing renewal, in which case the current certificate is not usable
|
||||
if timeLeft > 0 && !revoked {
|
||||
log.Debug("certificate expires soon but is already being renewed; serving current certificate",
|
||||
logger.Debug("certificate expires soon but is already being renewed; serving current certificate",
|
||||
zap.Strings("subjects", currentCert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
return currentCert, nil
|
||||
@ -647,7 +658,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
|
||||
// otherwise, we'll have to wait for the renewal to finish so we don't serve
|
||||
// a revoked or expired certificate
|
||||
|
||||
log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete",
|
||||
logger.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete",
|
||||
zap.Strings("subjects", currentCert.Names),
|
||||
zap.Time("expired", expiresAt(currentCert.Leaf)),
|
||||
zap.Bool("revoked", revoked))
|
||||
@ -678,7 +689,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
|
||||
obtainCertWaitChansMu.Unlock()
|
||||
}
|
||||
|
||||
log = log.With(
|
||||
logger = logger.With(
|
||||
zap.String("server_name", name),
|
||||
zap.Strings("subjects", currentCert.Names),
|
||||
zap.Time("expiration", expiresAt(currentCert.Leaf)),
|
||||
@ -699,19 +710,19 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
|
||||
cfg.certCache.mu.Unlock()
|
||||
unblockWaiters()
|
||||
|
||||
if log != nil {
|
||||
log.Error("certificate should not be obtained", zap.Error(err))
|
||||
if logger != nil {
|
||||
logger.Error("certificate should not be obtained", zap.Error(err))
|
||||
}
|
||||
|
||||
return Certificate{}, err
|
||||
}
|
||||
|
||||
log.Info("attempting certificate renewal")
|
||||
logger.Info("attempting certificate renewal")
|
||||
|
||||
// otherwise, renew with issuer, etc.
|
||||
var newCert Certificate
|
||||
if revoked {
|
||||
newCert, err = cfg.forceRenew(ctx, log, currentCert)
|
||||
newCert, err = cfg.forceRenew(ctx, logger, currentCert)
|
||||
} else {
|
||||
err = cfg.RenewCertAsync(ctx, name, false)
|
||||
if err == nil {
|
||||
@ -726,7 +737,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
|
||||
unblockWaiters()
|
||||
|
||||
if err != nil {
|
||||
log.Error("renewing and reloading certificate", zap.String("server_name", name), zap.Error(err))
|
||||
logger.Error("renewing and reloading certificate", zap.String("server_name", name), zap.Error(err))
|
||||
}
|
||||
|
||||
return newCert, err
|
||||
@ -753,16 +764,16 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
|
||||
return Certificate{}, nil
|
||||
}
|
||||
|
||||
var upstreamCert *tls.Certificate
|
||||
|
||||
// try all the GetCertificate methods on external managers; use first one that returns a certificate
|
||||
var upstreamCert *tls.Certificate
|
||||
var err error
|
||||
for i, certManager := range cfg.OnDemand.Managers {
|
||||
var err error
|
||||
upstreamCert, err = certManager.GetCertificate(ctx, hello)
|
||||
if err != nil {
|
||||
logger.Error("getting certificate from external certificate manager",
|
||||
logger.Error("external certificate manager",
|
||||
zap.String("sni", hello.ServerName),
|
||||
zap.Int("cert_manager", i),
|
||||
zap.String("cert_manager", fmt.Sprintf("%T", certManager)),
|
||||
zap.Int("cert_manager_idx", i),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
@ -770,14 +781,16 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return Certificate{}, fmt.Errorf("external certificate manager indicated that it is unable to yield certificate: %v", err)
|
||||
}
|
||||
if upstreamCert == nil {
|
||||
logger.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
|
||||
return Certificate{}, nil
|
||||
}
|
||||
|
||||
var cert Certificate
|
||||
err := fillCertFromLeaf(&cert, *upstreamCert)
|
||||
if err != nil {
|
||||
if err = fillCertFromLeaf(&cert, *upstreamCert); err != nil {
|
||||
return Certificate{}, fmt.Errorf("external certificate manager: %s: filling cert from leaf: %v", hello.ServerName, err)
|
||||
}
|
||||
|
||||
@ -822,10 +835,13 @@ func (cfg *Config) getTLSALPNChallengeCert(clientHello *tls.ClientHelloInfo) (*t
|
||||
// getNameFromClientHello returns a normalized form of hello.ServerName.
|
||||
// If hello.ServerName is empty (i.e. client did not use SNI), then the
|
||||
// associated connection's local address is used to extract an IP address.
|
||||
func (*Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string {
|
||||
func (cfg *Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string {
|
||||
if name := normalizedName(hello.ServerName); name != "" {
|
||||
return name
|
||||
}
|
||||
if cfg.DefaultServerName != "" {
|
||||
return normalizedName(cfg.DefaultServerName)
|
||||
}
|
||||
return localIPFromConn(hello.Conn)
|
||||
}
|
||||
|
||||
|
||||
@ -16,9 +16,10 @@ package certmagic
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@ -91,7 +92,7 @@ func solveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Reque
|
||||
challengeReqPath := challenge.HTTP01ResourcePath()
|
||||
if r.URL.Path == challengeReqPath &&
|
||||
strings.EqualFold(hostOnly(r.Host), challenge.Identifier.Value) && // mitigate DNS rebinding attacks
|
||||
r.Method == "GET" {
|
||||
r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Write([]byte(challenge.KeyAuthorization))
|
||||
r.Close = true
|
||||
@ -116,7 +117,94 @@ func SolveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Reque
|
||||
// LooksLikeHTTPChallenge returns true if r looks like an ACME
|
||||
// HTTP challenge request from an ACME server.
|
||||
func LooksLikeHTTPChallenge(r *http.Request) bool {
|
||||
return r.Method == "GET" && strings.HasPrefix(r.URL.Path, challengeBasePath)
|
||||
return r.Method == http.MethodGet &&
|
||||
strings.HasPrefix(r.URL.Path, acmeHTTPChallengeBasePath)
|
||||
}
|
||||
|
||||
const challengeBasePath = "/.well-known/acme-challenge"
|
||||
// LooksLikeZeroSSLHTTPValidation returns true if the request appears to be
|
||||
// domain validation from a ZeroSSL/Sectigo CA. NOTE: This API is
|
||||
// non-standard and is subject to change.
|
||||
func LooksLikeZeroSSLHTTPValidation(r *http.Request) bool {
|
||||
return r.Method == http.MethodGet &&
|
||||
strings.HasPrefix(r.URL.Path, zerosslHTTPValidationBasePath)
|
||||
}
|
||||
|
||||
// HTTPValidationHandler wraps the ZeroSSL HTTP validation handler such that
|
||||
// it can pass verification checks from ZeroSSL's API.
|
||||
//
|
||||
// If a request is not a ZeroSSL HTTP validation request, h will be invoked.
|
||||
func (iss *ZeroSSLIssuer) HTTPValidationHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if iss.HandleZeroSSLHTTPValidation(w, r) {
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleZeroSSLHTTPValidation is to ZeroSSL API HTTP validation requests like HandleHTTPChallenge
|
||||
// is to ACME HTTP challenge requests.
|
||||
func (iss *ZeroSSLIssuer) HandleZeroSSLHTTPValidation(w http.ResponseWriter, r *http.Request) bool {
|
||||
if iss == nil {
|
||||
return false
|
||||
}
|
||||
if !LooksLikeZeroSSLHTTPValidation(r) {
|
||||
return false
|
||||
}
|
||||
return iss.distributedHTTPValidationAnswer(w, r)
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) distributedHTTPValidationAnswer(w http.ResponseWriter, r *http.Request) bool {
|
||||
if iss == nil {
|
||||
return false
|
||||
}
|
||||
logger := iss.Logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
host := hostOnly(r.Host)
|
||||
valInfo, distributed, err := iss.getDistributedValidationInfo(r.Context(), host)
|
||||
if err != nil {
|
||||
logger.Error("looking up info for HTTP validation",
|
||||
zap.String("host", host),
|
||||
zap.String("remote_addr", r.RemoteAddr),
|
||||
zap.String("user_agent", r.Header.Get("User-Agent")),
|
||||
zap.Error(err))
|
||||
return false
|
||||
}
|
||||
return answerHTTPValidation(logger, w, r, valInfo, distributed)
|
||||
}
|
||||
|
||||
func answerHTTPValidation(logger *zap.Logger, rw http.ResponseWriter, req *http.Request, valInfo acme.Challenge, distributed bool) bool {
|
||||
// ensure URL matches
|
||||
validationURL, err := url.Parse(valInfo.URL)
|
||||
if err != nil {
|
||||
logger.Error("got invalid URL from CA",
|
||||
zap.String("file_validation_url", valInfo.URL),
|
||||
zap.Error(err))
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
if req.URL.Path != validationURL.Path {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return true
|
||||
}
|
||||
|
||||
rw.Header().Add("Content-Type", "text/plain")
|
||||
req.Close = true
|
||||
|
||||
rw.Write([]byte(valInfo.Token))
|
||||
|
||||
logger.Info("served HTTP validation credential",
|
||||
zap.String("validation_path", valInfo.URL),
|
||||
zap.String("challenge", "http-01"),
|
||||
zap.String("remote", req.RemoteAddr),
|
||||
zap.Bool("distributed", distributed))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
acmeHTTPChallengeBasePath = "/.well-known/acme-challenge"
|
||||
zerosslHTTPValidationBasePath = "/.well-known/pki-validation/"
|
||||
)
|
||||
225
vendor/github.com/caddyserver/certmagic/maintain.go
generated
vendored
225
vendor/github.com/caddyserver/certmagic/maintain.go
generated
vendored
@ -27,7 +27,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
@ -92,7 +92,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
|
||||
func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
log := certCache.logger.Named("maintenance")
|
||||
|
||||
// configs will hold a map of certificate name to the config
|
||||
// configs will hold a map of certificate hash to the config
|
||||
// to use when managing that certificate
|
||||
configs := make(map[string]*Config)
|
||||
|
||||
@ -102,7 +102,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
// words, our first iteration through the certificate cache does NOT
|
||||
// perform any operations--only queues them--so that more fine-grained
|
||||
// write locks may be obtained during the actual operations.
|
||||
var renewQueue, reloadQueue, deleteQueue []Certificate
|
||||
var renewQueue, reloadQueue, deleteQueue, ariQueue certList
|
||||
|
||||
certCache.mu.RLock()
|
||||
for certKey, cert := range certCache.cache {
|
||||
@ -135,22 +135,28 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// ACME-specific: see if if ACME Renewal Info (ARI) window needs refreshing
|
||||
if cert.ari.NeedsRefresh() {
|
||||
configs[cert.hash] = cfg
|
||||
ariQueue = append(ariQueue, cert)
|
||||
}
|
||||
|
||||
// if time is up or expires soon, we need to try to renew it
|
||||
if cert.NeedsRenewal(cfg) {
|
||||
configs[cert.Names[0]] = cfg
|
||||
configs[cert.hash] = cfg
|
||||
|
||||
// see if the certificate in storage has already been renewed, possibly by another
|
||||
// instance that didn't coordinate with this one; if so, just load it (this
|
||||
// might happen if another instance already renewed it - kinda sloppy but checking disk
|
||||
// first is a simple way to possibly drastically reduce rate limit problems)
|
||||
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(ctx, cert)
|
||||
storedCertNeedsRenew, err := cfg.managedCertInStorageNeedsRenewal(ctx, cert)
|
||||
if err != nil {
|
||||
// hmm, weird, but not a big deal, maybe it was deleted or something
|
||||
log.Warn("error while checking if stored certificate is also expiring soon",
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.Error(err))
|
||||
} else if !storedCertExpiring {
|
||||
// if the certificate is NOT expiring soon and there was no error, then we
|
||||
} else if !storedCertNeedsRenew {
|
||||
// if the certificate does NOT need renewal and there was no error, then we
|
||||
// are good to just reload the certificate from storage instead of repeating
|
||||
// a likely-unnecessary renewal procedure
|
||||
reloadQueue = append(reloadQueue, cert)
|
||||
@ -161,11 +167,30 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
// NOTE: It is super-important to note that the TLS-ALPN challenge requires
|
||||
// a write lock on the cache in order to complete its challenge, so it is extra
|
||||
// vital that this renew operation does not happen inside our read lock!
|
||||
renewQueue = append(renewQueue, cert)
|
||||
renewQueue.insert(cert)
|
||||
}
|
||||
}
|
||||
certCache.mu.RUnlock()
|
||||
|
||||
// Update ARI, and then for any certs where the ARI window changed,
|
||||
// be sure to queue them for renewal if necessary
|
||||
for _, cert := range ariQueue {
|
||||
cfg := configs[cert.hash]
|
||||
cert, changed, err := cfg.updateARI(ctx, cert, log)
|
||||
if err != nil {
|
||||
log.Error("updating ARI", zap.Error(err))
|
||||
}
|
||||
if changed && cert.NeedsRenewal(cfg) {
|
||||
// it's theoretically possible that another instance already got the memo
|
||||
// on the changed ARI and even renewed the cert already, and thus doing it
|
||||
// here is wasteful, but I have never heard of this happening in reality,
|
||||
// so to save some cycles for now I think we'll just queue it for renewal
|
||||
// (notice how we use 'insert' to avoid duplicates, in case it was already
|
||||
// scheduled for renewal anyway)
|
||||
renewQueue.insert(cert)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload certificates that merely need to be updated in memory
|
||||
for _, oldCert := range reloadQueue {
|
||||
timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
|
||||
@ -173,7 +198,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
zap.Strings("identifiers", oldCert.Names),
|
||||
zap.Duration("remaining", timeLeft))
|
||||
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
cfg := configs[oldCert.hash]
|
||||
|
||||
// crucially, this happens OUTSIDE a lock on the certCache
|
||||
_, err := cfg.reloadManagedCertificate(ctx, oldCert)
|
||||
@ -187,7 +212,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
|
||||
|
||||
// Renewal queue
|
||||
for _, oldCert := range renewQueue {
|
||||
cfg := configs[oldCert.Names[0]]
|
||||
cfg := configs[oldCert.hash]
|
||||
err := certCache.queueRenewalTask(ctx, oldCert, cfg)
|
||||
if err != nil {
|
||||
log.Error("queueing renewal task",
|
||||
@ -390,6 +415,171 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// storageHasNewerARI returns true if the configured storage has ARI that is newer
|
||||
// than that of a certificate that is already loaded, along with the value from
|
||||
// storage.
|
||||
func (cfg *Config) storageHasNewerARI(ctx context.Context, cert Certificate) (bool, acme.RenewalInfo, error) {
|
||||
storedCertData, err := cfg.loadStoredACMECertificateMetadata(ctx, cert)
|
||||
if err != nil || storedCertData.RenewalInfo == nil {
|
||||
return false, acme.RenewalInfo{}, err
|
||||
}
|
||||
// prefer stored info if it has a window and the loaded one doesn't,
|
||||
// or if the one in storage has a later RetryAfter (though I suppose
|
||||
// it's not guaranteed, typically those will move forward in time)
|
||||
if (!cert.ari.HasWindow() && storedCertData.RenewalInfo.HasWindow()) ||
|
||||
storedCertData.RenewalInfo.RetryAfter.After(*cert.ari.RetryAfter) {
|
||||
return true, *storedCertData.RenewalInfo, nil
|
||||
}
|
||||
return false, acme.RenewalInfo{}, nil
|
||||
}
|
||||
|
||||
// loadStoredACMECertificateMetadata loads the stored ACME certificate data
|
||||
// from the cert's sidecar JSON file.
|
||||
func (cfg *Config) loadStoredACMECertificateMetadata(ctx context.Context, cert Certificate) (acme.Certificate, error) {
|
||||
metaBytes, err := cfg.Storage.Load(ctx, StorageKeys.SiteMeta(cert.issuerKey, cert.Names[0]))
|
||||
if err != nil {
|
||||
return acme.Certificate{}, fmt.Errorf("loading cert metadata: %w", err)
|
||||
}
|
||||
|
||||
var certRes CertificateResource
|
||||
if err = json.Unmarshal(metaBytes, &certRes); err != nil {
|
||||
return acme.Certificate{}, fmt.Errorf("unmarshaling cert metadata: %w", err)
|
||||
}
|
||||
|
||||
var acmeCert acme.Certificate
|
||||
if err = json.Unmarshal(certRes.IssuerData, &acmeCert); err != nil {
|
||||
return acme.Certificate{}, fmt.Errorf("unmarshaling potential ACME issuer metadata: %v", err)
|
||||
}
|
||||
|
||||
return acmeCert, nil
|
||||
}
|
||||
|
||||
// updateARI updates the cert's ACME renewal info, first by checking storage for a newer
|
||||
// one, or getting it from the CA if needed. The updated info is stored in storage and
|
||||
// updated in the cache. The certificate with the updated ARI is returned. If true is
|
||||
// returned, the ARI window or selected time has changed, and the caller should check if
|
||||
// the cert needs to be renewed now, even if there is an error.
|
||||
func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.Logger) (updatedCert Certificate, changed bool, err error) {
|
||||
logger = logger.With(
|
||||
zap.Strings("identifiers", cert.Names),
|
||||
zap.String("cert_hash", cert.hash),
|
||||
zap.String("ari_unique_id", cert.ari.UniqueIdentifier),
|
||||
zap.Time("cert_expiry", cert.Leaf.NotAfter))
|
||||
|
||||
updatedCert = cert
|
||||
oldARI := cert.ari
|
||||
|
||||
// see if the stored value has been refreshed already by another instance
|
||||
gotNewARI, newARI, err := cfg.storageHasNewerARI(ctx, cert)
|
||||
|
||||
// when we're all done, log if something about the schedule is different
|
||||
// ("WARN" level because ARI window changing may be a sign of external trouble
|
||||
// and we want to draw their attention to a potential explanation URL)
|
||||
defer func() {
|
||||
changed = !newARI.SameWindow(oldARI)
|
||||
|
||||
if changed {
|
||||
logger.Warn("ARI window or selected renewal time changed",
|
||||
zap.Time("prev_start", oldARI.SuggestedWindow.Start),
|
||||
zap.Time("next_start", newARI.SuggestedWindow.Start),
|
||||
zap.Time("prev_end", oldARI.SuggestedWindow.End),
|
||||
zap.Time("next_end", newARI.SuggestedWindow.End),
|
||||
zap.Time("prev_selected_time", oldARI.SelectedTime),
|
||||
zap.Time("next_selected_time", newARI.SelectedTime),
|
||||
zap.String("explanation_url", newARI.ExplanationURL))
|
||||
}
|
||||
}()
|
||||
|
||||
if err == nil && gotNewARI {
|
||||
// great, storage has a newer one we can use
|
||||
cfg.certCache.mu.Lock()
|
||||
updatedCert = cfg.certCache.cache[cert.hash]
|
||||
updatedCert.ari = newARI
|
||||
cfg.certCache.cache[cert.hash] = updatedCert
|
||||
cfg.certCache.mu.Unlock()
|
||||
logger.Info("reloaded ARI with newer one in storage",
|
||||
zap.Timep("next_refresh", newARI.RetryAfter),
|
||||
zap.Time("renewal_time", newARI.SelectedTime))
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("error while checking storage for updated ARI; updating ARI now", zap.Error(err))
|
||||
}
|
||||
|
||||
// of the issuers configured, hopefully one of them is the ACME CA we got the cert from
|
||||
for _, iss := range cfg.Issuers {
|
||||
if acmeIss, ok := iss.(*ACMEIssuer); ok {
|
||||
newARI, err = acmeIss.getRenewalInfo(ctx, cert) // be sure to use existing newARI variable so we can compare against old value in the defer
|
||||
if err != nil {
|
||||
// could be anything, but a common error might simply be the "wrong" ACME CA
|
||||
// (meaning, different from the one that issued the cert, thus the only one
|
||||
// that would have any ARI for it) if multiple ACME CAs are configured
|
||||
logger.Error("failed updating renewal info from ACME CA",
|
||||
zap.String("issuer", iss.IssuerKey()),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// when we get the latest ARI, the acme package will select a time within the window
|
||||
// for us; of course, since it's random, it's likely different from the previously-
|
||||
// selected time; but if the window doesn't change, there's no need to change the
|
||||
// selected time (the acme package doesn't know the previous window to know better)
|
||||
// ... so if the window hasn't changed we'll just put back the selected time
|
||||
if newARI.SameWindow(oldARI) && !oldARI.SelectedTime.IsZero() {
|
||||
newARI.SelectedTime = oldARI.SelectedTime
|
||||
}
|
||||
|
||||
// then store the updated ARI (even if the window didn't change, the Retry-After
|
||||
// likely did) in cache and storage
|
||||
|
||||
// be sure we get the cert from the cache while inside a lock to avoid logical races
|
||||
cfg.certCache.mu.Lock()
|
||||
updatedCert = cfg.certCache.cache[cert.hash]
|
||||
updatedCert.ari = newARI
|
||||
cfg.certCache.cache[cert.hash] = updatedCert
|
||||
cfg.certCache.mu.Unlock()
|
||||
|
||||
// update the ARI value in storage
|
||||
var certData acme.Certificate
|
||||
certData, err = cfg.loadStoredACMECertificateMetadata(ctx, cert)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("got new ARI from %s, but failed loading stored certificate metadata: %v", iss.IssuerKey(), err)
|
||||
return
|
||||
}
|
||||
certData.RenewalInfo = &newARI
|
||||
var certDataBytes, certResBytes []byte
|
||||
certDataBytes, err = json.Marshal(certData)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("got new ARI from %s, but failed marshaling certificate ACME metadata: %v", iss.IssuerKey(), err)
|
||||
return
|
||||
}
|
||||
certResBytes, err = json.MarshalIndent(CertificateResource{
|
||||
SANs: cert.Names,
|
||||
IssuerData: certDataBytes,
|
||||
}, "", "\t")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("got new ARI from %s, but could not re-encode certificate metadata: %v", iss.IssuerKey(), err)
|
||||
return
|
||||
}
|
||||
if err = cfg.Storage.Store(ctx, StorageKeys.SiteMeta(cert.issuerKey, cert.Names[0]), certResBytes); err != nil {
|
||||
err = fmt.Errorf("got new ARI from %s, but could not store it with certificate metadata: %v", iss.IssuerKey(), err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("updated ACME renewal information",
|
||||
zap.Time("selected_time", newARI.SelectedTime),
|
||||
zap.Timep("next_update", newARI.RetryAfter),
|
||||
zap.String("explanation_url", newARI.ExplanationURL))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = fmt.Errorf("could not fully update ACME renewal info: either no ACME issuer configured for certificate, or all failed (make sure the ACME CA that issued the certificate is configured)")
|
||||
return
|
||||
}
|
||||
|
||||
// CleanStorageOptions specifies how to clean up a storage unit.
|
||||
type CleanStorageOptions struct {
|
||||
// Optional custom logger.
|
||||
@ -452,7 +642,7 @@ func CleanStorage(ctx context.Context, storage Storage, opts CleanStorageOptions
|
||||
lastTLSClean := lastClean["tls"]
|
||||
if time.Since(lastTLSClean.Timestamp) < opts.Interval {
|
||||
nextTime := time.Now().Add(opts.Interval)
|
||||
opts.Logger.Warn("storage cleaning happened too recently; skipping for now",
|
||||
opts.Logger.Info("storage cleaning happened too recently; skipping for now",
|
||||
zap.String("instance", lastTLSClean.InstanceID),
|
||||
zap.Time("try_again", nextTime),
|
||||
zap.Duration("try_again_in", time.Until(nextTime)),
|
||||
@ -725,6 +915,19 @@ func certShouldBeForceRenewed(cert Certificate) bool {
|
||||
cert.ocsp.Status == ocsp.Revoked
|
||||
}
|
||||
|
||||
type certList []Certificate
|
||||
|
||||
// insert appends cert to the list if it is not already in the list.
|
||||
// Efficiency: O(n)
|
||||
func (certs *certList) insert(cert Certificate) {
|
||||
for _, c := range *certs {
|
||||
if c.hash == cert.hash {
|
||||
return
|
||||
}
|
||||
}
|
||||
*certs = append(*certs, cert)
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultRenewCheckInterval is how often to check certificates for expiration.
|
||||
// Scans are very lightweight, so this can be semi-frequent. This default should
|
||||
|
||||
16
vendor/github.com/caddyserver/certmagic/ocsp.go
generated
vendored
16
vendor/github.com/caddyserver/certmagic/ocsp.go
generated
vendored
@ -168,12 +168,24 @@ func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Respons
|
||||
return nil, nil, fmt.Errorf("override disables querying OCSP responder: %v", issuedCert.OCSPServer[0])
|
||||
}
|
||||
|
||||
// configure HTTP client if necessary
|
||||
httpClient := http.DefaultClient
|
||||
if ocspConfig.HTTPProxy != nil {
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: ocspConfig.HTTPProxy,
|
||||
},
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// get issuer certificate if needed
|
||||
if len(certificates) == 1 {
|
||||
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||
return nil, nil, fmt.Errorf("no URL to issuing certificate")
|
||||
}
|
||||
|
||||
resp, err := http.Get(issuedCert.IssuingCertificateURL[0])
|
||||
resp, err := httpClient.Get(issuedCert.IssuingCertificateURL[0])
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("getting issuer certificate: %v", err)
|
||||
}
|
||||
@ -202,7 +214,7 @@ func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Respons
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(ocspReq)
|
||||
req, err := http.Post(respURL, "application/ocsp-request", reader)
|
||||
req, err := httpClient.Post(respURL, "application/ocsp-request", reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("making OCSP request: %v", err)
|
||||
}
|
||||
|
||||
291
vendor/github.com/caddyserver/certmagic/solvers.go
generated
vendored
291
vendor/github.com/caddyserver/certmagic/solvers.go
generated
vendored
@ -30,9 +30,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/libdns/libdns"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// httpSolver solves the HTTP challenge. It must be
|
||||
@ -46,9 +47,9 @@ import (
|
||||
// can access the keyAuth material is by loading it
|
||||
// from storage, which is done by distributedSolver.
|
||||
type httpSolver struct {
|
||||
closed int32 // accessed atomically
|
||||
acmeIssuer *ACMEIssuer
|
||||
address string
|
||||
closed int32 // accessed atomically
|
||||
handler http.Handler
|
||||
address string
|
||||
}
|
||||
|
||||
// Present starts an HTTP server if none is already listening on s.address.
|
||||
@ -88,7 +89,7 @@ func (s *httpSolver) serve(ctx context.Context, si *solverInfo) {
|
||||
}()
|
||||
defer close(si.done)
|
||||
httpServer := &http.Server{
|
||||
Handler: s.acmeIssuer.HTTPChallengeHandler(http.NewServeMux()),
|
||||
Handler: s.handler,
|
||||
BaseContext: func(listener net.Listener) context.Context { return ctx },
|
||||
}
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
@ -250,9 +251,92 @@ func (s *tlsALPNSolver) CleanUp(_ context.Context, chal acme.Challenge) error {
|
||||
// DNS provider APIs and implementations of the libdns interfaces must also
|
||||
// support multiple same-named TXT records.
|
||||
type DNS01Solver struct {
|
||||
DNSManager
|
||||
}
|
||||
|
||||
// Present creates the DNS TXT record for the given ACME challenge.
|
||||
func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
zrec, err := s.DNSManager.createRecord(ctx, dnsName, "TXT", keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remember the record and zone we got so we can clean up more efficiently
|
||||
s.saveDNSPresentMemory(dnsPresentMemory{
|
||||
dnsName: dnsName,
|
||||
zoneRec: zrec,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks until the TXT record created in Present() appears in
|
||||
// authoritative lookups, i.e. until it has propagated, or until
|
||||
// timeout, whichever is first.
|
||||
func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error {
|
||||
// prepare for the checks by determining what to look for
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
// wait for the record to propagate
|
||||
memory, err := s.getDNSPresentMemory(dnsName, "TXT", keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.DNSManager.wait(ctx, memory.zoneRec)
|
||||
}
|
||||
|
||||
// CleanUp deletes the DNS TXT record created in Present().
|
||||
//
|
||||
// We ignore the context because cleanup is often/likely performed after
|
||||
// a context cancellation, and properly-implemented DNS providers should
|
||||
// honor cancellation, which would result in cleanup being aborted.
|
||||
// Cleanup must always occur.
|
||||
func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
// always forget about the record so we don't leak memory
|
||||
defer s.deleteDNSPresentMemory(dnsName, keyAuth)
|
||||
|
||||
// recall the record we created and zone we looked up
|
||||
memory, err := s.getDNSPresentMemory(dnsName, "TXT", keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.DNSManager.cleanUpRecord(ctx, memory.zoneRec); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSManager is a type that makes libdns providers usable for performing
|
||||
// DNS verification. See https://github.com/libdns/libdns
|
||||
//
|
||||
// Note that records may be manipulated concurrently by some clients (such as
|
||||
// acmez, which CertMagic uses), meaning that multiple records may be created
|
||||
// in a DNS zone simultaneously, and in some cases distinct records of the same
|
||||
// type may have the same name. For example, solving ACME challenges for both example.com
|
||||
// and *.example.com create a TXT record named _acme_challenge.example.com,
|
||||
// but with different tokens as their values. This solver distinguishes between
|
||||
// different records with the same type and name by looking at their values.
|
||||
type DNSManager struct {
|
||||
// The implementation that interacts with the DNS
|
||||
// provider to set or delete records. (REQUIRED)
|
||||
DNSProvider ACMEDNSProvider
|
||||
DNSProvider DNSProvider
|
||||
|
||||
// The TTL for the temporary challenge records.
|
||||
TTL time.Duration
|
||||
@ -274,6 +358,9 @@ type DNS01Solver struct {
|
||||
// that the solver doesn't follow CNAME/NS record.
|
||||
OverrideDomain string
|
||||
|
||||
// An optional logger.
|
||||
Logger *zap.Logger
|
||||
|
||||
// Remember DNS records while challenges are active; i.e.
|
||||
// records we have presented and not yet cleaned up.
|
||||
// This lets us clean them up quickly and efficiently.
|
||||
@ -285,83 +372,81 @@ type DNS01Solver struct {
|
||||
// the value of their TXT records, which should contain
|
||||
// unique challenge tokens.
|
||||
// See https://github.com/caddyserver/caddy/issues/3474.
|
||||
txtRecords map[string][]dnsPresentMemory
|
||||
txtRecordsMu sync.Mutex
|
||||
records map[string][]dnsPresentMemory
|
||||
recordsMu sync.Mutex
|
||||
}
|
||||
|
||||
// Present creates the DNS TXT record for the given ACME challenge.
|
||||
func (s *DNS01Solver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
func (m *DNSManager) createRecord(ctx context.Context, dnsName, recordType, recordValue string) (zoneRecord, error) {
|
||||
logger := m.logger()
|
||||
|
||||
zone, err := findZoneByFQDN(dnsName, recursiveNameservers(s.Resolvers))
|
||||
zone, err := findZoneByFQDN(logger, dnsName, recursiveNameservers(m.Resolvers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err)
|
||||
return zoneRecord{}, fmt.Errorf("could not determine zone for domain %q: %v", dnsName, err)
|
||||
}
|
||||
|
||||
rec := libdns.Record{
|
||||
Type: "TXT",
|
||||
Type: recordType,
|
||||
Name: libdns.RelativeName(dnsName+".", zone),
|
||||
Value: keyAuth,
|
||||
TTL: s.TTL,
|
||||
Value: recordValue,
|
||||
TTL: m.TTL,
|
||||
}
|
||||
|
||||
results, err := s.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec})
|
||||
logger.Debug("creating DNS record",
|
||||
zap.String("dns_name", dnsName),
|
||||
zap.String("zone", zone),
|
||||
zap.String("record_name", rec.Name),
|
||||
zap.String("record_type", rec.Type),
|
||||
zap.String("record_value", rec.Value),
|
||||
zap.Duration("record_ttl", rec.TTL))
|
||||
|
||||
results, err := m.DNSProvider.AppendRecords(ctx, zone, []libdns.Record{rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding temporary record for zone %q: %w", zone, err)
|
||||
return zoneRecord{}, fmt.Errorf("adding temporary record for zone %q: %w", zone, err)
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("expected one record, got %d: %v", len(results), results)
|
||||
return zoneRecord{}, fmt.Errorf("expected one record, got %d: %v", len(results), results)
|
||||
}
|
||||
|
||||
// remember the record and zone we got so we can clean up more efficiently
|
||||
s.saveDNSPresentMemory(dnsPresentMemory{
|
||||
dnsZone: zone,
|
||||
dnsName: dnsName,
|
||||
rec: results[0],
|
||||
})
|
||||
|
||||
return nil
|
||||
return zoneRecord{zone, results[0]}, nil
|
||||
}
|
||||
|
||||
// Wait blocks until the TXT record created in Present() appears in
|
||||
// wait blocks until the TXT record created in Present() appears in
|
||||
// authoritative lookups, i.e. until it has propagated, or until
|
||||
// timeout, whichever is first.
|
||||
func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error {
|
||||
func (m *DNSManager) wait(ctx context.Context, zrec zoneRecord) error {
|
||||
logger := m.logger()
|
||||
|
||||
// if configured to, pause before doing propagation checks
|
||||
// (even if they are disabled, the wait might be desirable on its own)
|
||||
if s.PropagationDelay > 0 {
|
||||
if m.PropagationDelay > 0 {
|
||||
select {
|
||||
case <-time.After(s.PropagationDelay):
|
||||
case <-time.After(m.PropagationDelay):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// skip propagation checks if configured to do so
|
||||
if s.PropagationTimeout == -1 {
|
||||
if m.PropagationTimeout == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepare for the checks by determining what to look for
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
// timings
|
||||
timeout := s.PropagationTimeout
|
||||
timeout := m.PropagationTimeout
|
||||
if timeout == 0 {
|
||||
timeout = defaultDNSPropagationTimeout
|
||||
}
|
||||
const interval = 2 * time.Second
|
||||
|
||||
// how we'll do the checks
|
||||
resolvers := recursiveNameservers(s.Resolvers)
|
||||
checkAuthoritativeServers := len(m.Resolvers) == 0
|
||||
resolvers := recursiveNameservers(m.Resolvers)
|
||||
|
||||
recType := dns.TypeTXT
|
||||
if zrec.record.Type == "CNAME" {
|
||||
recType = dns.TypeCNAME
|
||||
}
|
||||
|
||||
absName := libdns.AbsoluteName(zrec.record.Name, zrec.zone)
|
||||
|
||||
var err error
|
||||
start := time.Now()
|
||||
@ -371,10 +456,17 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
logger.Debug("checking DNS propagation",
|
||||
zap.String("fqdn", absName),
|
||||
zap.String("record_type", zrec.record.Type),
|
||||
zap.String("expected_value", zrec.record.Value),
|
||||
zap.Strings("resolvers", resolvers))
|
||||
|
||||
var ready bool
|
||||
ready, err = checkDNSPropagation(dnsName, keyAuth, resolvers)
|
||||
ready, err = checkDNSPropagation(logger, absName, recType, zrec.record.Value, checkAuthoritativeServers, resolvers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking DNS propagation of %q: %w", dnsName, err)
|
||||
return fmt.Errorf("checking DNS propagation of %q (relative=%s zone=%s resolvers=%v): %w", absName, zrec.record.Name, zrec.zone, resolvers, err)
|
||||
}
|
||||
if ready {
|
||||
return nil
|
||||
@ -384,101 +476,110 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
|
||||
return fmt.Errorf("timed out waiting for record to fully propagate; verify DNS provider configuration is correct - last error: %v", err)
|
||||
}
|
||||
|
||||
type zoneRecord struct {
|
||||
zone string
|
||||
record libdns.Record
|
||||
}
|
||||
|
||||
// CleanUp deletes the DNS TXT record created in Present().
|
||||
//
|
||||
// We ignore the context because cleanup is often/likely performed after
|
||||
// a context cancellation, and properly-implemented DNS providers should
|
||||
// honor cancellation, which would result in cleanup being aborted.
|
||||
// Cleanup must always occur.
|
||||
func (s *DNS01Solver) CleanUp(_ context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
if s.OverrideDomain != "" {
|
||||
dnsName = s.OverrideDomain
|
||||
}
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
|
||||
// always forget about the record so we don't leak memory
|
||||
defer s.deleteDNSPresentMemory(dnsName, keyAuth)
|
||||
|
||||
// recall the record we created and zone we looked up
|
||||
memory, err := s.getDNSPresentMemory(dnsName, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (m *DNSManager) cleanUpRecord(_ context.Context, zrec zoneRecord) error {
|
||||
logger := m.logger()
|
||||
|
||||
// clean up the record - use a different context though, since
|
||||
// one common reason cleanup is performed is because a context
|
||||
// was canceled, and if so, any HTTP requests by this provider
|
||||
// should fail if the provider is properly implemented
|
||||
// (see issue #200)
|
||||
timeout := s.PropagationTimeout
|
||||
timeout := m.PropagationTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = defaultDNSPropagationTimeout
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
_, err = s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting temporary record for name %q in zone %q: %w", memory.dnsName, memory.dnsZone, err)
|
||||
}
|
||||
|
||||
logger.Debug("deleting DNS record",
|
||||
zap.String("zone", zrec.zone),
|
||||
zap.String("record_id", zrec.record.ID),
|
||||
zap.String("record_name", zrec.record.Name),
|
||||
zap.String("record_type", zrec.record.Type),
|
||||
zap.String("record_value", zrec.record.Value))
|
||||
|
||||
_, err := m.DNSProvider.DeleteRecords(ctx, zrec.zone, []libdns.Record{zrec.record})
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting temporary record for name %q in zone %q: %w", zrec.zone, zrec.record, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DNSManager) logger() *zap.Logger {
|
||||
logger := m.Logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
return logger.Named("dns_manager")
|
||||
}
|
||||
|
||||
const defaultDNSPropagationTimeout = 2 * time.Minute
|
||||
|
||||
// dnsPresentMemory associates a created DNS record with its zone
|
||||
// (since libdns Records are zone-relative and do not include zone).
|
||||
type dnsPresentMemory struct {
|
||||
dnsZone string
|
||||
dnsName string
|
||||
rec libdns.Record
|
||||
zoneRec zoneRecord
|
||||
}
|
||||
|
||||
func (s *DNS01Solver) saveDNSPresentMemory(mem dnsPresentMemory) {
|
||||
s.txtRecordsMu.Lock()
|
||||
if s.txtRecords == nil {
|
||||
s.txtRecords = make(map[string][]dnsPresentMemory)
|
||||
func (s *DNSManager) saveDNSPresentMemory(mem dnsPresentMemory) {
|
||||
s.recordsMu.Lock()
|
||||
if s.records == nil {
|
||||
s.records = make(map[string][]dnsPresentMemory)
|
||||
}
|
||||
s.txtRecords[mem.dnsName] = append(s.txtRecords[mem.dnsName], mem)
|
||||
s.txtRecordsMu.Unlock()
|
||||
s.records[mem.dnsName] = append(s.records[mem.dnsName], mem)
|
||||
s.recordsMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *DNS01Solver) getDNSPresentMemory(dnsName, keyAuth string) (dnsPresentMemory, error) {
|
||||
s.txtRecordsMu.Lock()
|
||||
defer s.txtRecordsMu.Unlock()
|
||||
func (s *DNSManager) getDNSPresentMemory(dnsName, recType, value string) (dnsPresentMemory, error) {
|
||||
s.recordsMu.Lock()
|
||||
defer s.recordsMu.Unlock()
|
||||
|
||||
var memory dnsPresentMemory
|
||||
for _, mem := range s.txtRecords[dnsName] {
|
||||
if mem.rec.Value == keyAuth {
|
||||
for _, mem := range s.records[dnsName] {
|
||||
if mem.zoneRec.record.Type == recType && mem.zoneRec.record.Value == value {
|
||||
memory = mem
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if memory.rec.Name == "" {
|
||||
if memory.zoneRec.record.Name == "" {
|
||||
return dnsPresentMemory{}, fmt.Errorf("no memory of presenting a DNS record for %q (usually OK if presenting also failed)", dnsName)
|
||||
}
|
||||
|
||||
return memory, nil
|
||||
}
|
||||
|
||||
func (s *DNS01Solver) deleteDNSPresentMemory(dnsName, keyAuth string) {
|
||||
s.txtRecordsMu.Lock()
|
||||
defer s.txtRecordsMu.Unlock()
|
||||
func (s *DNSManager) deleteDNSPresentMemory(dnsName, keyAuth string) {
|
||||
s.recordsMu.Lock()
|
||||
defer s.recordsMu.Unlock()
|
||||
|
||||
for i, mem := range s.txtRecords[dnsName] {
|
||||
if mem.rec.Value == keyAuth {
|
||||
s.txtRecords[dnsName] = append(s.txtRecords[dnsName][:i], s.txtRecords[dnsName][i+1:]...)
|
||||
for i, mem := range s.records[dnsName] {
|
||||
if mem.zoneRec.record.Value == keyAuth {
|
||||
s.records[dnsName] = append(s.records[dnsName][:i], s.records[dnsName][i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACMEDNSProvider defines the set of operations required for
|
||||
// ACME challenges. A DNS provider must be able to append and
|
||||
// delete records in order to solve ACME challenges. Find one
|
||||
// you can use at https://github.com/libdns. If your provider
|
||||
// isn't implemented yet, feel free to contribute!
|
||||
type ACMEDNSProvider interface {
|
||||
// DNSProvider defines the set of operations required for
|
||||
// ACME challenges or other sorts of domain verification.
|
||||
// A DNS provider must be able to append and delete records
|
||||
// in order to solve ACME challenges. Find one you can use
|
||||
// at https://github.com/libdns. If your provider isn't
|
||||
// implemented yet, feel free to contribute!
|
||||
type DNSProvider interface {
|
||||
libdns.RecordAppender
|
||||
libdns.RecordDeleter
|
||||
}
|
||||
|
||||
2
vendor/github.com/caddyserver/certmagic/storage.go
generated
vendored
2
vendor/github.com/caddyserver/certmagic/storage.go
generated
vendored
@ -289,7 +289,7 @@ func acquireLock(ctx context.Context, storage Storage, lockKey string) error {
|
||||
}
|
||||
|
||||
func releaseLock(ctx context.Context, storage Storage, lockKey string) error {
|
||||
err := storage.Unlock(context.TODO(), lockKey) // TODO: in Go 1.21, use WithoutCancel (see #247)
|
||||
err := storage.Unlock(context.WithoutCancel(ctx), lockKey)
|
||||
if err == nil {
|
||||
locksMu.Lock()
|
||||
delete(locks, lockKey)
|
||||
|
||||
304
vendor/github.com/caddyserver/certmagic/zerosslissuer.go
generated
vendored
Normal file
304
vendor/github.com/caddyserver/certmagic/zerosslissuer.go
generated
vendored
Normal file
@ -0,0 +1,304 @@
|
||||
// Copyright 2015 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package certmagic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/zerossl"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ZeroSSLIssuer can get certificates from ZeroSSL's API. (To use ZeroSSL's ACME
|
||||
// endpoint, use the ACMEIssuer instead.) Note that use of the API is restricted
|
||||
// by payment tier.
|
||||
type ZeroSSLIssuer struct {
|
||||
// The API key (or "access key") for using the ZeroSSL API.
|
||||
// REQUIRED.
|
||||
APIKey string
|
||||
|
||||
// How many days the certificate should be valid for.
|
||||
ValidityDays int
|
||||
|
||||
// The host to bind to when opening a listener for
|
||||
// verifying domain names (or IPs).
|
||||
ListenHost string
|
||||
|
||||
// If HTTP is forwarded from port 80, specify the
|
||||
// forwarded port here.
|
||||
AltHTTPPort int
|
||||
|
||||
// To use CNAME validation instead of HTTP
|
||||
// validation, set this field.
|
||||
CNAMEValidation *DNSManager
|
||||
|
||||
// Where to store verification material temporarily.
|
||||
// Set this on all instances in a cluster to the same
|
||||
// value to enable distributed verification.
|
||||
Storage Storage
|
||||
|
||||
// An optional (but highly recommended) logger.
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
// Issue obtains a certificate for the given csr.
|
||||
func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*IssuedCertificate, error) {
|
||||
client := iss.getClient()
|
||||
|
||||
identifiers := namesFromCSR(csr)
|
||||
if len(identifiers) == 0 {
|
||||
return nil, fmt.Errorf("no identifiers on CSR")
|
||||
}
|
||||
|
||||
logger := iss.Logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
logger = logger.With(zap.Strings("identifiers", identifiers))
|
||||
|
||||
logger.Info("creating certificate")
|
||||
|
||||
cert, err := client.CreateCertificate(ctx, csr, iss.ValidityDays)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating certificate: %v", err)
|
||||
}
|
||||
|
||||
logger = logger.With(zap.String("cert_id", cert.ID))
|
||||
logger.Info("created certificate")
|
||||
|
||||
defer func(certID string) {
|
||||
if err != nil {
|
||||
err := client.CancelCertificate(context.WithoutCancel(ctx), certID)
|
||||
if err == nil {
|
||||
logger.Info("canceled certificate")
|
||||
} else {
|
||||
logger.Error("unable to cancel certificate", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}(cert.ID)
|
||||
|
||||
var verificationMethod zerossl.VerificationMethod
|
||||
|
||||
if iss.CNAMEValidation == nil {
|
||||
verificationMethod = zerossl.HTTPVerification
|
||||
logger = logger.With(zap.String("verification_method", string(verificationMethod)))
|
||||
|
||||
httpVerifier := &httpSolver{
|
||||
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(iss.getHTTPPort())),
|
||||
handler: iss.HTTPValidationHandler(http.NewServeMux()),
|
||||
}
|
||||
|
||||
var solver acmez.Solver = httpVerifier
|
||||
if iss.Storage != nil {
|
||||
solver = distributedSolver{
|
||||
storage: iss.Storage,
|
||||
storageKeyIssuerPrefix: iss.IssuerKey(),
|
||||
solver: httpVerifier,
|
||||
}
|
||||
}
|
||||
|
||||
// since the distributed solver was originally designed for ACME,
|
||||
// the API is geared around ACME challenges. ZeroSSL's HTTP validation
|
||||
// is very similar to the HTTP challenge, but not quite compatible,
|
||||
// so we kind of shim the ZeroSSL validation data into a Challenge
|
||||
// object... it is not a perfect use of this type but it's pretty close
|
||||
valInfo := cert.Validation.OtherMethods[identifiers[0]]
|
||||
fakeChallenge := acme.Challenge{
|
||||
Identifier: acme.Identifier{
|
||||
Value: identifiers[0], // used for storage key
|
||||
},
|
||||
URL: valInfo.FileValidationURLHTTP,
|
||||
Token: strings.Join(cert.Validation.OtherMethods[identifiers[0]].FileValidationContent, "\n"),
|
||||
}
|
||||
if err = solver.Present(ctx, fakeChallenge); err != nil {
|
||||
return nil, fmt.Errorf("presenting validation file for verification: %v", err)
|
||||
}
|
||||
defer solver.CleanUp(ctx, fakeChallenge)
|
||||
} else {
|
||||
verificationMethod = zerossl.CNAMEVerification
|
||||
logger = logger.With(zap.String("verification_method", string(verificationMethod)))
|
||||
|
||||
// create the CNAME record(s)
|
||||
records := make(map[string]zoneRecord, len(cert.Validation.OtherMethods))
|
||||
for name, verifyInfo := range cert.Validation.OtherMethods {
|
||||
zr, err := iss.CNAMEValidation.createRecord(ctx, verifyInfo.CnameValidationP1, "CNAME", verifyInfo.CnameValidationP2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating CNAME record: %v", err)
|
||||
}
|
||||
defer func(name string, zr zoneRecord) {
|
||||
if err := iss.CNAMEValidation.cleanUpRecord(ctx, zr); err != nil {
|
||||
logger.Warn("cleaning up temporary validation record failed",
|
||||
zap.String("dns_name", name),
|
||||
zap.Error(err))
|
||||
}
|
||||
}(name, zr)
|
||||
records[name] = zr
|
||||
}
|
||||
|
||||
// wait for them to propagate
|
||||
for name, zr := range records {
|
||||
if err := iss.CNAMEValidation.wait(ctx, zr); err != nil {
|
||||
// allow it, since the CA will ultimately decide, but definitely log it
|
||||
logger.Warn("failed CNAME record propagation check", zap.String("domain", name), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("validating identifiers")
|
||||
|
||||
cert, err = client.VerifyIdentifiers(ctx, cert.ID, verificationMethod, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("verifying identifiers: %v", err)
|
||||
}
|
||||
|
||||
switch cert.Status {
|
||||
case "pending_validation":
|
||||
logger.Info("validations succeeded; waiting for certificate to be issued")
|
||||
|
||||
cert, err = iss.waitForCertToBeIssued(ctx, client, cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for certificate to be issued: %v", err)
|
||||
}
|
||||
case "issued":
|
||||
logger.Info("validations succeeded; downloading certificate bundle")
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected certificate status: %s", cert.Status)
|
||||
}
|
||||
|
||||
bundle, err := client.DownloadCertificate(ctx, cert.ID, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("downloading certificate: %v", err)
|
||||
}
|
||||
|
||||
logger.Info("successfully downloaded issued certificate")
|
||||
|
||||
return &IssuedCertificate{
|
||||
Certificate: []byte(bundle.CertificateCrt + bundle.CABundleCrt),
|
||||
Metadata: cert,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.Client, cert zerossl.CertificateObject) (zerossl.CertificateObject, error) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return cert, ctx.Err()
|
||||
case <-ticker.C:
|
||||
var err error
|
||||
cert, err = client.GetCertificate(ctx, cert.ID)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
if cert.Status == "issued" {
|
||||
return cert, nil
|
||||
}
|
||||
if cert.Status != "pending_validation" {
|
||||
return cert, fmt.Errorf("unexpected certificate status: %s", cert.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) getClient() zerossl.Client {
|
||||
return zerossl.Client{AccessKey: iss.APIKey}
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) getHTTPPort() int {
|
||||
useHTTPPort := HTTPChallengePort
|
||||
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
|
||||
useHTTPPort = HTTPPort
|
||||
}
|
||||
if iss.AltHTTPPort > 0 {
|
||||
useHTTPPort = iss.AltHTTPPort
|
||||
}
|
||||
return useHTTPPort
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for ZeroSSL.
|
||||
func (iss *ZeroSSLIssuer) IssuerKey() string { return zerosslIssuerKey }
|
||||
|
||||
// Revoke revokes the given certificate. Only do this if there is a security or trust
|
||||
// concern with the certificate.
|
||||
func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert CertificateResource, reason int) error {
|
||||
r := zerossl.UnspecifiedReason
|
||||
switch reason {
|
||||
case acme.ReasonKeyCompromise:
|
||||
r = zerossl.KeyCompromise
|
||||
case acme.ReasonAffiliationChanged:
|
||||
r = zerossl.AffiliationChanged
|
||||
case acme.ReasonSuperseded:
|
||||
r = zerossl.Superseded
|
||||
case acme.ReasonCessationOfOperation:
|
||||
r = zerossl.CessationOfOperation
|
||||
default:
|
||||
return fmt.Errorf("unsupported reason: %d", reason)
|
||||
}
|
||||
var certObj zerossl.CertificateObject
|
||||
if err := json.Unmarshal(cert.IssuerData, &certObj); err != nil {
|
||||
return err
|
||||
}
|
||||
return iss.getClient().RevokeCertificate(ctx, certObj.ID, r)
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) getDistributedValidationInfo(ctx context.Context, identifier string) (acme.Challenge, bool, error) {
|
||||
ds := distributedSolver{
|
||||
storage: iss.Storage,
|
||||
storageKeyIssuerPrefix: StorageKeys.Safe(iss.IssuerKey()),
|
||||
}
|
||||
tokenKey := ds.challengeTokensKey(identifier)
|
||||
|
||||
valObjectBytes, err := iss.Storage.Load(ctx, tokenKey)
|
||||
if err != nil {
|
||||
return acme.Challenge{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err)
|
||||
}
|
||||
|
||||
if len(valObjectBytes) == 0 {
|
||||
return acme.Challenge{}, false, fmt.Errorf("no information found to solve challenge for identifier: %s", identifier)
|
||||
}
|
||||
|
||||
// since the distributed solver's API is geared around ACME challenges,
|
||||
// we crammed the validation info into a Challenge object
|
||||
var chal acme.Challenge
|
||||
if err = json.Unmarshal(valObjectBytes, &chal); err != nil {
|
||||
return acme.Challenge{}, false, fmt.Errorf("decoding HTTP validation token file %s (corrupted?): %v", tokenKey, err)
|
||||
}
|
||||
|
||||
return chal, true, nil
|
||||
}
|
||||
|
||||
const (
|
||||
zerosslAPIBase = "https://" + zerossl.BaseURL + "/acme"
|
||||
zerosslValidationPathPrefix = "/.well-known/pki-validation/"
|
||||
zerosslIssuerKey = "zerossl"
|
||||
)
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ Issuer = (*ZeroSSLIssuer)(nil)
|
||||
_ Revoker = (*ZeroSSLIssuer)(nil)
|
||||
)
|
||||
2
vendor/github.com/caddyserver/zerossl/.gitignore
generated
vendored
Normal file
2
vendor/github.com/caddyserver/zerossl/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
_gitignore
|
||||
.DS_Store
|
||||
21
vendor/github.com/caddyserver/zerossl/LICENSE
generated
vendored
Normal file
21
vendor/github.com/caddyserver/zerossl/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Matthew Holt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
6
vendor/github.com/caddyserver/zerossl/README.md
generated
vendored
Normal file
6
vendor/github.com/caddyserver/zerossl/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
ZeroSSL API client [](https://pkg.go.dev/github.com/caddyserver/zerossl)
|
||||
==================
|
||||
|
||||
This package implements the [ZeroSSL REST API](https://zerossl.com/documentation/api/) in Go.
|
||||
|
||||
The REST API is distinct from the [ACME endpoint](https://zerossl.com/documentation/acme/), which is a standardized way of obtaining certificates.
|
||||
170
vendor/github.com/caddyserver/zerossl/client.go
generated
vendored
Normal file
170
vendor/github.com/caddyserver/zerossl/client.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
package zerossl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client acts as a ZeroSSL API client. It facilitates ZeroSSL certificate operations.
|
||||
type Client struct {
|
||||
// REQUIRED: Your ZeroSSL account access key.
|
||||
AccessKey string `json:"access_key"`
|
||||
|
||||
// Optionally adjust the base URL of the API.
|
||||
// Default: https://api.zerossl.com
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
|
||||
// Optionally configure a custom HTTP client.
|
||||
HTTPClient *http.Client `json:"-"`
|
||||
}
|
||||
|
||||
func (c Client) httpGet(ctx context.Context, endpoint string, qs url.Values, target any) error {
|
||||
url := c.url(endpoint, qs)
|
||||
return c.httpRequest(ctx, http.MethodGet, url, nil, target)
|
||||
}
|
||||
|
||||
func (c Client) httpPost(ctx context.Context, endpoint string, qs url.Values, payload, target any) error {
|
||||
var reqBody io.Reader
|
||||
if payload != nil {
|
||||
payloadJSON, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqBody = bytes.NewReader(payloadJSON)
|
||||
}
|
||||
url := c.url(endpoint, qs)
|
||||
return c.httpRequest(ctx, http.MethodPost, url, reqBody, target)
|
||||
}
|
||||
|
||||
func (c Client) httpRequest(ctx context.Context, method, reqURL string, reqBody io.Reader, target any) error {
|
||||
r, err := http.NewRequestWithContext(ctx, method, reqURL, reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reqBody != nil {
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.httpClient().Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// because the ZeroSSL API doesn't use HTTP status codes to indicate an error,
|
||||
// nor does each response body have a consistent way of detecting success/error,
|
||||
// we have to implement a hack: download the entire response body and try
|
||||
// decoding it as JSON in a way that errors if there's any unknown fields
|
||||
// (such as "success"), because if there is an unkown field, either our model
|
||||
// is outdated, or there was an error payload in the response instead of the
|
||||
// expected structure, so we then try again to decode to an error struct
|
||||
respBytes, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading response body: %v", err)
|
||||
}
|
||||
|
||||
// assume success first by trying to decode payload into output target
|
||||
dec := json.NewDecoder(bytes.NewReader(respBytes))
|
||||
dec.DisallowUnknownFields() // important hacky hack so we can detect an error payload
|
||||
originalDecodeErr := dec.Decode(&target)
|
||||
if originalDecodeErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// could have gotten any kind of error, really; but assuming valid JSON,
|
||||
// most likely it is an error payload
|
||||
var apiError APIError
|
||||
if err := json.NewDecoder(bytes.NewReader(respBytes)).Decode(&apiError); err != nil {
|
||||
return fmt.Errorf("request succeeded, but decoding JSON response failed: %v (raw=%s)", err, respBytes)
|
||||
}
|
||||
|
||||
// successfully got an error! or did we?
|
||||
if apiError.Success {
|
||||
return apiError // ummm... why are we getting an error if it was successful ??? is this not really an error?
|
||||
}
|
||||
|
||||
// remove access_key from URL so it doesn't leak into logs
|
||||
u, err := url.Parse(reqURL)
|
||||
if err != nil {
|
||||
reqURL = fmt.Sprintf("<invalid url: %v>", err)
|
||||
}
|
||||
if u != nil {
|
||||
q, err := url.ParseQuery(u.RawQuery)
|
||||
if err == nil {
|
||||
q.Set(accessKeyParam, "redacted")
|
||||
u.RawQuery = q.Encode()
|
||||
reqURL = u.String()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s %s: HTTP %d: %v (raw=%s decode_error=%v)", method, reqURL, resp.StatusCode, apiError, respBytes, originalDecodeErr)
|
||||
}
|
||||
|
||||
func (c Client) url(endpoint string, qs url.Values) string {
|
||||
baseURL := c.BaseURL
|
||||
if baseURL == "" {
|
||||
baseURL = BaseURL
|
||||
}
|
||||
|
||||
// for consistency, ensure endpoint starts with /
|
||||
// and base URL does NOT end with /.
|
||||
if !strings.HasPrefix(endpoint, "/") {
|
||||
endpoint = "/" + endpoint
|
||||
}
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
if qs == nil {
|
||||
qs = url.Values{}
|
||||
}
|
||||
qs.Set(accessKeyParam, c.AccessKey)
|
||||
|
||||
return fmt.Sprintf("%s%s?%s", baseURL, endpoint, qs.Encode())
|
||||
}
|
||||
|
||||
func (c Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return httpClient
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Timeout: 2 * time.Minute,
|
||||
}
|
||||
|
||||
// anyBool is a hacky type that accepts true or 1 (or their string variants),
|
||||
// or "yes" or "y", and any casing variants of the same, as a boolean true when
|
||||
// unmarshaling JSON. Everything else is boolean false.
|
||||
//
|
||||
// This is needed due to type inconsistencies in ZeroSSL's API with "success" values.
|
||||
type anyBool bool
|
||||
|
||||
// UnmarshalJSON satisfies json.Unmarshaler according to
|
||||
// this type's documentation.
|
||||
func (ab *anyBool) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
switch strings.ToLower(string(b)) {
|
||||
case `true`, `"true"`, `1`, `"1"`, `"yes"`, `"y"`:
|
||||
*ab = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals ab to either true or false.
|
||||
func (ab *anyBool) MarshalJSON() ([]byte, error) {
|
||||
if ab != nil && *ab {
|
||||
return []byte("true"), nil
|
||||
}
|
||||
return []byte("false"), nil
|
||||
}
|
||||
|
||||
const accessKeyParam = "access_key"
|
||||
270
vendor/github.com/caddyserver/zerossl/endpoints.go
generated
vendored
Normal file
270
vendor/github.com/caddyserver/zerossl/endpoints.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
||||
package zerossl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateCertificate creates a certificate. After creating a certificate, its identifiers must be verified before
|
||||
// the certificate can be downloaded. The CSR must have been fully created using x509.CreateCertificateRequest
|
||||
// (its Raw field must be filled out).
|
||||
func (c Client) CreateCertificate(ctx context.Context, csr *x509.CertificateRequest, validityDays int) (CertificateObject, error) {
|
||||
payload := struct {
|
||||
CertificateDomains string `json:"certificate_domains"`
|
||||
CertificateCSR string `json:"certificate_csr"`
|
||||
CertificateValidityDays int `json:"certificate_validity_days,omitempty"`
|
||||
StrictDomains int `json:"strict_domains,omitempty"`
|
||||
ReplacementForCertificate string `json:"replacement_for_certificate,omitempty"`
|
||||
}{
|
||||
CertificateDomains: strings.Join(identifiersFromCSR(csr), ","),
|
||||
CertificateCSR: csr2pem(csr.Raw),
|
||||
CertificateValidityDays: validityDays,
|
||||
StrictDomains: 1,
|
||||
}
|
||||
|
||||
var result CertificateObject
|
||||
if err := c.httpPost(ctx, "/certificates", nil, payload, &result); err != nil {
|
||||
return CertificateObject{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// VerifyIdentifiers tells ZeroSSL that you are ready to prove control over your domain/IP using the method specified.
|
||||
// The credentials from CreateCertificate must be used to verify identifiers. At least one email is required if using
|
||||
// email verification method.
|
||||
func (c Client) VerifyIdentifiers(ctx context.Context, certificateID string, method VerificationMethod, emails []string) (CertificateObject, error) {
|
||||
payload := struct {
|
||||
ValidationMethod VerificationMethod `json:"validation_method"`
|
||||
ValidationEmail string `json:"validation_email,omitempty"`
|
||||
}{
|
||||
ValidationMethod: method,
|
||||
}
|
||||
if method == EmailVerification && len(emails) > 0 {
|
||||
payload.ValidationEmail = strings.Join(emails, ",")
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("/certificates/%s/challenges", url.QueryEscape(certificateID))
|
||||
|
||||
var result CertificateObject
|
||||
if err := c.httpPost(ctx, endpoint, nil, payload, &result); err != nil {
|
||||
return CertificateObject{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DownloadCertificateFile writes the certificate bundle as a zip file to the provided output writer.
|
||||
func (c Client) DownloadCertificateFile(ctx context.Context, certificateID string, includeCrossSigned bool, output io.Writer) error {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/download", url.QueryEscape(certificateID))
|
||||
|
||||
qs := url.Values{}
|
||||
if includeCrossSigned {
|
||||
qs.Set("include_cross_signed", "1")
|
||||
}
|
||||
|
||||
url := c.url(endpoint, qs)
|
||||
r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient().Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(output, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) DownloadCertificate(ctx context.Context, certificateID string, includeCrossSigned bool) (CertificateBundle, error) {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/download/return", url.QueryEscape(certificateID))
|
||||
|
||||
qs := url.Values{}
|
||||
if includeCrossSigned {
|
||||
qs.Set("include_cross_signed", "1")
|
||||
}
|
||||
|
||||
var result CertificateBundle
|
||||
if err := c.httpGet(ctx, endpoint, qs, &result); err != nil {
|
||||
return CertificateBundle{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c Client) GetCertificate(ctx context.Context, certificateID string) (CertificateObject, error) {
|
||||
endpoint := fmt.Sprintf("/certificates/%s", url.QueryEscape(certificateID))
|
||||
|
||||
var result CertificateObject
|
||||
if err := c.httpGet(ctx, endpoint, nil, &result); err != nil {
|
||||
return CertificateObject{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListCertificateParameters specifies how to search or list certificates on the account.
|
||||
// An empty set of parameters will return no results.
|
||||
type ListCertificatesParameters struct {
|
||||
// Return certificates with this status.
|
||||
Status string
|
||||
|
||||
// Return these types of certificates.
|
||||
Type string
|
||||
|
||||
// The CommonName or SAN.
|
||||
Search string
|
||||
|
||||
// The page number. Default: 1
|
||||
Page int
|
||||
|
||||
// How many per page. Default: 100
|
||||
Limit int
|
||||
}
|
||||
|
||||
func (c Client) ListCertificates(ctx context.Context, params ListCertificatesParameters) (CertificateList, error) {
|
||||
qs := url.Values{}
|
||||
if params.Status != "" {
|
||||
qs.Set("certificate_status", params.Status)
|
||||
}
|
||||
if params.Type != "" {
|
||||
qs.Set("certificate_type", params.Type)
|
||||
}
|
||||
if params.Search != "" {
|
||||
qs.Set("search", params.Search)
|
||||
}
|
||||
if params.Limit != 0 {
|
||||
qs.Set("limit", strconv.Itoa(params.Limit))
|
||||
}
|
||||
if params.Page != 0 {
|
||||
qs.Set("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
|
||||
var result CertificateList
|
||||
if err := c.httpGet(ctx, "/certificates", qs, &result); err != nil {
|
||||
return CertificateList{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c Client) VerificationStatus(ctx context.Context, certificateID string) (ValidationStatus, error) {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/status", url.QueryEscape(certificateID))
|
||||
|
||||
var result ValidationStatus
|
||||
if err := c.httpGet(ctx, endpoint, nil, &result); err != nil {
|
||||
return ValidationStatus{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c Client) ResendVerificationEmail(ctx context.Context, certificateID string) error {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/challenges/email", url.QueryEscape(certificateID))
|
||||
|
||||
var result struct {
|
||||
Success anyBool `json:"success"`
|
||||
}
|
||||
if err := c.httpGet(ctx, endpoint, nil, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return fmt.Errorf("got %v without any error status", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only revoke a certificate if the private key is compromised, the certificate was a mistake, or
|
||||
// the identifiers are no longer in use. Do not revoke a certificate when renewing it.
|
||||
func (c Client) RevokeCertificate(ctx context.Context, certificateID string, reason RevocationReason) error {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/revoke", url.QueryEscape(certificateID))
|
||||
|
||||
qs := url.Values{"reason": []string{string(reason)}}
|
||||
|
||||
var result struct {
|
||||
Success anyBool `json:"success"`
|
||||
}
|
||||
if err := c.httpGet(ctx, endpoint, qs, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return fmt.Errorf("got %v without any error status", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelCertificate cancels a certificate that has not been issued yet (is in draft or pending_validation state).
|
||||
func (c Client) CancelCertificate(ctx context.Context, certificateID string) error {
|
||||
endpoint := fmt.Sprintf("/certificates/%s/cancel", url.QueryEscape(certificateID))
|
||||
|
||||
var result struct {
|
||||
Success anyBool `json:"success"`
|
||||
}
|
||||
if err := c.httpPost(ctx, endpoint, nil, nil, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return fmt.Errorf("got %v without any error status", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateCSR sends the CSR to ZeroSSL for validation. Pass in the ASN.1 DER-encoded bytes;
|
||||
// this is found in x509.CertificateRequest.Raw after calling x5p9.CreateCertificateRequest.
|
||||
func (c Client) ValidateCSR(ctx context.Context, csrASN1DER []byte) error {
|
||||
payload := struct {
|
||||
CSR string `json:"csr"`
|
||||
}{
|
||||
CSR: csr2pem(csrASN1DER),
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Valid bool `json:"valid"`
|
||||
Error any `json:"error"`
|
||||
}
|
||||
if err := c.httpPost(ctx, "/validation/csr", nil, payload, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid {
|
||||
return fmt.Errorf("invalid CSR: %v", result.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) GenerateEABCredentials(ctx context.Context) (keyID, hmacKey string, err error) {
|
||||
var result struct {
|
||||
APIError
|
||||
EABKID string `json:"eab_kid"`
|
||||
EABHMACKey string `json:"eab_hmac_key"`
|
||||
}
|
||||
err = c.httpPost(ctx, "/acme/eab-credentials", nil, nil, &result)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !result.Success {
|
||||
err = fmt.Errorf("failed to create EAB credentials: %v", result.APIError)
|
||||
}
|
||||
return result.EABKID, result.EABHMACKey, err
|
||||
}
|
||||
94
vendor/github.com/caddyserver/zerossl/models.go
generated
vendored
Normal file
94
vendor/github.com/caddyserver/zerossl/models.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package zerossl
|
||||
|
||||
import "fmt"
|
||||
|
||||
type APIError struct {
|
||||
Success anyBool `json:"success"`
|
||||
ErrorInfo struct {
|
||||
Code int `json:"code"`
|
||||
Type string `json:"type"`
|
||||
|
||||
// for domain verification only; each domain is grouped into its
|
||||
// www and non-www variant for CNAME validation, or its URL
|
||||
// for HTTP validation
|
||||
Details map[string]map[string]ValidationError `json:"details"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (ae APIError) Error() string {
|
||||
if ae.ErrorInfo.Code == 0 && ae.ErrorInfo.Type == "" && len(ae.ErrorInfo.Details) == 0 {
|
||||
return "<missing error info>"
|
||||
}
|
||||
return fmt.Sprintf("API error %d: %s (details=%v)",
|
||||
ae.ErrorInfo.Code, ae.ErrorInfo.Type, ae.ErrorInfo.Details)
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
CNAMEValidationError
|
||||
HTTPValidationError
|
||||
}
|
||||
|
||||
type CNAMEValidationError struct {
|
||||
CNAMEFound int `json:"cname_found"`
|
||||
RecordCorrect int `json:"record_correct"`
|
||||
TargetHost string `json:"target_host"`
|
||||
TargetRecord string `json:"target_record"`
|
||||
ActualRecord string `json:"actual_record"`
|
||||
}
|
||||
|
||||
type HTTPValidationError struct {
|
||||
FileFound int `json:"file_found"`
|
||||
Error bool `json:"error"`
|
||||
ErrorSlug string `json:"error_slug"`
|
||||
ErrorInfo string `json:"error_info"`
|
||||
}
|
||||
|
||||
type CertificateObject struct {
|
||||
ID string `json:"id"` // "certificate hash"
|
||||
Type string `json:"type"`
|
||||
CommonName string `json:"common_name"`
|
||||
AdditionalDomains string `json:"additional_domains"`
|
||||
Created string `json:"created"`
|
||||
Expires string `json:"expires"`
|
||||
Status string `json:"status"`
|
||||
ValidationType *string `json:"validation_type,omitempty"`
|
||||
ValidationEmails *string `json:"validation_emails,omitempty"`
|
||||
ReplacementFor string `json:"replacement_for,omitempty"`
|
||||
FingerprintSHA1 *string `json:"fingerprint_sha1"`
|
||||
BrandValidation any `json:"brand_validation"`
|
||||
Validation *struct {
|
||||
EmailValidation map[string][]string `json:"email_validation,omitempty"`
|
||||
OtherMethods map[string]ValidationObject `json:"other_methods,omitempty"`
|
||||
} `json:"validation,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationObject struct {
|
||||
FileValidationURLHTTP string `json:"file_validation_url_http"`
|
||||
FileValidationURLHTTPS string `json:"file_validation_url_https"`
|
||||
FileValidationContent []string `json:"file_validation_content"`
|
||||
CnameValidationP1 string `json:"cname_validation_p1"`
|
||||
CnameValidationP2 string `json:"cname_validation_p2"`
|
||||
}
|
||||
|
||||
type CertificateBundle struct {
|
||||
CertificateCrt string `json:"certificate.crt"`
|
||||
CABundleCrt string `json:"ca_bundle.crt"`
|
||||
}
|
||||
|
||||
type CertificateList struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
ResultCount int `json:"result_count"`
|
||||
Page string `json:"page"` // don't ask me why this is a string
|
||||
Limit int `json:"limit"`
|
||||
ACMEUsageLevel string `json:"acmeUsageLevel"`
|
||||
ACMELocked bool `json:"acmeLocked"`
|
||||
Results []CertificateObject `json:"results"`
|
||||
}
|
||||
|
||||
type ValidationStatus struct {
|
||||
ValidationCompleted int `json:"validation_completed"`
|
||||
Details map[string]struct {
|
||||
Method string `json:"method"`
|
||||
Status string `json:"status"`
|
||||
} `json:"details"`
|
||||
}
|
||||
64
vendor/github.com/caddyserver/zerossl/zerossl.go
generated
vendored
Normal file
64
vendor/github.com/caddyserver/zerossl/zerossl.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
// Package zerossl implements the ZeroSSL REST API.
|
||||
// See the API documentation on the ZeroSSL website: https://zerossl.com/documentation/api/
|
||||
package zerossl
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// The base URL to the ZeroSSL API.
|
||||
const BaseURL = "https://api.zerossl.com"
|
||||
|
||||
// ListAllCertificates returns parameters that lists all the certificates on the account;
|
||||
// be sure to set Page and Limit if paginating.
|
||||
func ListAllCertificates() ListCertificatesParameters {
|
||||
return ListCertificatesParameters{
|
||||
Status: "draft,pending_validation,issued,cancelled,revoked,expired",
|
||||
}
|
||||
}
|
||||
|
||||
func identifiersFromCSR(csr *x509.CertificateRequest) []string {
|
||||
var identifiers []string
|
||||
if csr.Subject.CommonName != "" {
|
||||
// deprecated for like 20 years, but oh well
|
||||
identifiers = append(identifiers, csr.Subject.CommonName)
|
||||
}
|
||||
identifiers = append(identifiers, csr.DNSNames...)
|
||||
identifiers = append(identifiers, csr.EmailAddresses...)
|
||||
for _, ip := range csr.IPAddresses {
|
||||
identifiers = append(identifiers, ip.String())
|
||||
}
|
||||
for _, uri := range csr.URIs {
|
||||
identifiers = append(identifiers, uri.String())
|
||||
}
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func csr2pem(csrASN1DER []byte) string {
|
||||
return fmt.Sprintf("-----BEGIN CERTIFICATE REQUEST-----\n%s\n-----END CERTIFICATE REQUEST-----",
|
||||
base64.StdEncoding.EncodeToString(csrASN1DER))
|
||||
}
|
||||
|
||||
// VerificationMethod represents a way of verifying identifiers with ZeroSSL.
|
||||
type VerificationMethod string
|
||||
|
||||
// Verification methods.
|
||||
const (
|
||||
EmailVerification VerificationMethod = "EMAIL"
|
||||
CNAMEVerification VerificationMethod = "CNAME_CSR_HASH"
|
||||
HTTPVerification VerificationMethod = "HTTP_CSR_HASH"
|
||||
HTTPSVerification VerificationMethod = "HTTPS_CSR_HASH"
|
||||
)
|
||||
|
||||
// RevocationReason represents various reasons for revoking a certificate.
|
||||
type RevocationReason string
|
||||
|
||||
const (
|
||||
UnspecifiedReason RevocationReason = "unspecified" // default
|
||||
KeyCompromise RevocationReason = "keyCompromise" // lost control of private key
|
||||
AffiliationChanged RevocationReason = "affiliationChanged" // identify information changed
|
||||
Superseded RevocationReason = "Superseded" // certificate replaced -- do not revoke for this reason, however
|
||||
CessationOfOperation RevocationReason = "cessationOfOperation" // domains are no longer in use
|
||||
)
|
||||
56
vendor/github.com/casbin/casbin/v2/enforcer_distributed.go
generated
vendored
56
vendor/github.com/casbin/casbin/v2/enforcer_distributed.go
generated
vendored
@ -34,19 +34,25 @@ func (d *DistributedEnforcer) AddPoliciesSelf(shouldPersist func() bool, sec str
|
||||
if shouldPersist != nil && shouldPersist() {
|
||||
var noExistsPolicy [][]string
|
||||
for _, rule := range rules {
|
||||
if !d.model.HasPolicy(sec, ptype, rule) {
|
||||
var hasPolicy bool
|
||||
hasPolicy, err = d.model.HasPolicy(sec, ptype, rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasPolicy {
|
||||
noExistsPolicy = append(noExistsPolicy, rule)
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, noExistsPolicy); err != nil {
|
||||
if err.Error() != notImplemented {
|
||||
return nil, err
|
||||
}
|
||||
if err = d.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, noExistsPolicy); err != nil && err.Error() != notImplemented {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
affected = d.model.AddPoliciesWithAffected(sec, ptype, rules)
|
||||
affected, err = d.model.AddPoliciesWithAffected(sec, ptype, rules)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
err := d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, affected)
|
||||
@ -71,7 +77,10 @@ func (d *DistributedEnforcer) RemovePoliciesSelf(shouldPersist func() bool, sec
|
||||
}
|
||||
}
|
||||
|
||||
affected = d.model.RemovePoliciesWithAffected(sec, ptype, rules)
|
||||
affected, err = d.model.RemovePoliciesWithAffected(sec, ptype, rules)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
err = d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)
|
||||
@ -89,14 +98,17 @@ func (d *DistributedEnforcer) RemoveFilteredPolicySelf(shouldPersist func() bool
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if shouldPersist != nil && shouldPersist() {
|
||||
if err := d.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
|
||||
if err = d.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
|
||||
if err.Error() != notImplemented {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, affected = d.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
|
||||
_, affected, err = d.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)
|
||||
@ -129,15 +141,15 @@ func (d *DistributedEnforcer) UpdatePolicySelf(shouldPersist func() bool, sec st
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if shouldPersist != nil && shouldPersist() {
|
||||
err := d.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
err = d.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
ruleUpdated := d.model.UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
if !ruleUpdated {
|
||||
return ruleUpdated, nil
|
||||
ruleUpdated, err := d.model.UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
if !ruleUpdated || err != nil {
|
||||
return ruleUpdated, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -159,15 +171,15 @@ func (d *DistributedEnforcer) UpdatePoliciesSelf(shouldPersist func() bool, sec
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if shouldPersist != nil && shouldPersist() {
|
||||
err := d.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
err = d.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
ruleUpdated := d.model.UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
if !ruleUpdated {
|
||||
return ruleUpdated, nil
|
||||
ruleUpdated, err := d.model.UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
if !ruleUpdated || err != nil {
|
||||
return ruleUpdated, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -199,8 +211,14 @@ func (d *DistributedEnforcer) UpdateFilteredPoliciesSelf(shouldPersist func() bo
|
||||
}
|
||||
}
|
||||
|
||||
ruleChanged := !d.model.RemovePolicies(sec, ptype, oldRules)
|
||||
d.model.AddPolicies(sec, ptype, newRules)
|
||||
ruleChanged, err := d.model.RemovePolicies(sec, ptype, oldRules)
|
||||
if err != nil {
|
||||
return ruleChanged, err
|
||||
}
|
||||
err = d.model.AddPolicies(sec, ptype, newRules)
|
||||
if err != nil {
|
||||
return ruleChanged, err
|
||||
}
|
||||
ruleChanged = ruleChanged && len(newRules) != 0
|
||||
if !ruleChanged {
|
||||
return ruleChanged, nil
|
||||
|
||||
46
vendor/github.com/casbin/casbin/v2/enforcer_interface.go
generated
vendored
46
vendor/github.com/casbin/casbin/v2/enforcer_interface.go
generated
vendored
@ -70,7 +70,7 @@ type IEnforcer interface {
|
||||
DeletePermissionForUser(user string, permission ...string) (bool, error)
|
||||
DeletePermissionsForUser(user string) (bool, error)
|
||||
GetPermissionsForUser(user string, domain ...string) ([][]string, error)
|
||||
HasPermissionForUser(user string, permission ...string) bool
|
||||
HasPermissionForUser(user string, permission ...string) (bool, error)
|
||||
GetImplicitRolesForUser(name string, domain ...string) ([]string, error)
|
||||
GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error)
|
||||
GetImplicitUsersForPermission(permission ...string) ([]string, error)
|
||||
@ -86,32 +86,32 @@ type IEnforcer interface {
|
||||
GetPermissionsForUserInDomain(user string, domain string) [][]string
|
||||
AddRoleForUserInDomain(user string, role string, domain string) (bool, error)
|
||||
DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error)
|
||||
GetAllUsersByDomain(domain string) []string
|
||||
GetAllUsersByDomain(domain string) ([]string, error)
|
||||
DeleteRolesForUserInDomain(user string, domain string) (bool, error)
|
||||
DeleteAllUsersByDomain(domain string) (bool, error)
|
||||
DeleteDomains(domains ...string) (bool, error)
|
||||
GetAllDomains() ([]string, error)
|
||||
GetAllRolesByDomain(domain string) []string
|
||||
GetAllRolesByDomain(domain string) ([]string, error)
|
||||
|
||||
/* Management API */
|
||||
GetAllSubjects() []string
|
||||
GetAllNamedSubjects(ptype string) []string
|
||||
GetAllObjects() []string
|
||||
GetAllNamedObjects(ptype string) []string
|
||||
GetAllActions() []string
|
||||
GetAllNamedActions(ptype string) []string
|
||||
GetAllRoles() []string
|
||||
GetAllNamedRoles(ptype string) []string
|
||||
GetPolicy() [][]string
|
||||
GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string
|
||||
GetNamedPolicy(ptype string) [][]string
|
||||
GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string
|
||||
GetGroupingPolicy() [][]string
|
||||
GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string
|
||||
GetNamedGroupingPolicy(ptype string) [][]string
|
||||
GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string
|
||||
HasPolicy(params ...interface{}) bool
|
||||
HasNamedPolicy(ptype string, params ...interface{}) bool
|
||||
GetAllSubjects() ([]string, error)
|
||||
GetAllNamedSubjects(ptype string) ([]string, error)
|
||||
GetAllObjects() ([]string, error)
|
||||
GetAllNamedObjects(ptype string) ([]string, error)
|
||||
GetAllActions() ([]string, error)
|
||||
GetAllNamedActions(ptype string) ([]string, error)
|
||||
GetAllRoles() ([]string, error)
|
||||
GetAllNamedRoles(ptype string) ([]string, error)
|
||||
GetPolicy() ([][]string, error)
|
||||
GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)
|
||||
GetNamedPolicy(ptype string) ([][]string, error)
|
||||
GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)
|
||||
GetGroupingPolicy() ([][]string, error)
|
||||
GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)
|
||||
GetNamedGroupingPolicy(ptype string) ([][]string, error)
|
||||
GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)
|
||||
HasPolicy(params ...interface{}) (bool, error)
|
||||
HasNamedPolicy(ptype string, params ...interface{}) (bool, error)
|
||||
AddPolicy(params ...interface{}) (bool, error)
|
||||
AddPolicies(rules [][]string) (bool, error)
|
||||
AddNamedPolicy(ptype string, params ...interface{}) (bool, error)
|
||||
@ -124,8 +124,8 @@ type IEnforcer interface {
|
||||
RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error)
|
||||
RemoveNamedPolicies(ptype string, rules [][]string) (bool, error)
|
||||
RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error)
|
||||
HasGroupingPolicy(params ...interface{}) bool
|
||||
HasNamedGroupingPolicy(ptype string, params ...interface{}) bool
|
||||
HasGroupingPolicy(params ...interface{}) (bool, error)
|
||||
HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)
|
||||
AddGroupingPolicy(params ...interface{}) (bool, error)
|
||||
AddGroupingPolicies(rules [][]string) (bool, error)
|
||||
AddGroupingPoliciesEx(rules [][]string) (bool, error)
|
||||
|
||||
40
vendor/github.com/casbin/casbin/v2/enforcer_synced.go
generated
vendored
40
vendor/github.com/casbin/casbin/v2/enforcer_synced.go
generated
vendored
@ -233,126 +233,126 @@ func (e *SyncedEnforcer) BatchEnforceWithMatcher(matcher string, requests [][]in
|
||||
}
|
||||
|
||||
// GetAllSubjects gets the list of subjects that show up in the current policy.
|
||||
func (e *SyncedEnforcer) GetAllSubjects() []string {
|
||||
func (e *SyncedEnforcer) GetAllSubjects() ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllSubjects()
|
||||
}
|
||||
|
||||
// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.
|
||||
func (e *SyncedEnforcer) GetAllNamedSubjects(ptype string) []string {
|
||||
func (e *SyncedEnforcer) GetAllNamedSubjects(ptype string) ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllNamedSubjects(ptype)
|
||||
}
|
||||
|
||||
// GetAllObjects gets the list of objects that show up in the current policy.
|
||||
func (e *SyncedEnforcer) GetAllObjects() []string {
|
||||
func (e *SyncedEnforcer) GetAllObjects() ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllObjects()
|
||||
}
|
||||
|
||||
// GetAllNamedObjects gets the list of objects that show up in the current named policy.
|
||||
func (e *SyncedEnforcer) GetAllNamedObjects(ptype string) []string {
|
||||
func (e *SyncedEnforcer) GetAllNamedObjects(ptype string) ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllNamedObjects(ptype)
|
||||
}
|
||||
|
||||
// GetAllActions gets the list of actions that show up in the current policy.
|
||||
func (e *SyncedEnforcer) GetAllActions() []string {
|
||||
func (e *SyncedEnforcer) GetAllActions() ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllActions()
|
||||
}
|
||||
|
||||
// GetAllNamedActions gets the list of actions that show up in the current named policy.
|
||||
func (e *SyncedEnforcer) GetAllNamedActions(ptype string) []string {
|
||||
func (e *SyncedEnforcer) GetAllNamedActions(ptype string) ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllNamedActions(ptype)
|
||||
}
|
||||
|
||||
// GetAllRoles gets the list of roles that show up in the current policy.
|
||||
func (e *SyncedEnforcer) GetAllRoles() []string {
|
||||
func (e *SyncedEnforcer) GetAllRoles() ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllRoles()
|
||||
}
|
||||
|
||||
// GetAllNamedRoles gets the list of roles that show up in the current named policy.
|
||||
func (e *SyncedEnforcer) GetAllNamedRoles(ptype string) []string {
|
||||
func (e *SyncedEnforcer) GetAllNamedRoles(ptype string) ([]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetAllNamedRoles(ptype)
|
||||
}
|
||||
|
||||
// GetPolicy gets all the authorization rules in the policy.
|
||||
func (e *SyncedEnforcer) GetPolicy() [][]string {
|
||||
func (e *SyncedEnforcer) GetPolicy() ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetPolicy()
|
||||
}
|
||||
|
||||
// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.
|
||||
func (e *SyncedEnforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *SyncedEnforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetFilteredPolicy(fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetNamedPolicy gets all the authorization rules in the named policy.
|
||||
func (e *SyncedEnforcer) GetNamedPolicy(ptype string) [][]string {
|
||||
func (e *SyncedEnforcer) GetNamedPolicy(ptype string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetNamedPolicy(ptype)
|
||||
}
|
||||
|
||||
// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.
|
||||
func (e *SyncedEnforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *SyncedEnforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetFilteredNamedPolicy(ptype, fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetGroupingPolicy gets all the role inheritance rules in the policy.
|
||||
func (e *SyncedEnforcer) GetGroupingPolicy() [][]string {
|
||||
func (e *SyncedEnforcer) GetGroupingPolicy() ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetGroupingPolicy()
|
||||
}
|
||||
|
||||
// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
|
||||
func (e *SyncedEnforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *SyncedEnforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetFilteredGroupingPolicy(fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.
|
||||
func (e *SyncedEnforcer) GetNamedGroupingPolicy(ptype string) [][]string {
|
||||
func (e *SyncedEnforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetNamedGroupingPolicy(ptype)
|
||||
}
|
||||
|
||||
// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
|
||||
func (e *SyncedEnforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *SyncedEnforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.GetFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// HasPolicy determines whether an authorization rule exists.
|
||||
func (e *SyncedEnforcer) HasPolicy(params ...interface{}) bool {
|
||||
func (e *SyncedEnforcer) HasPolicy(params ...interface{}) (bool, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.HasPolicy(params...)
|
||||
}
|
||||
|
||||
// HasNamedPolicy determines whether a named authorization rule exists.
|
||||
func (e *SyncedEnforcer) HasNamedPolicy(ptype string, params ...interface{}) bool {
|
||||
func (e *SyncedEnforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.HasNamedPolicy(ptype, params...)
|
||||
@ -493,14 +493,14 @@ func (e *SyncedEnforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int,
|
||||
}
|
||||
|
||||
// HasGroupingPolicy determines whether a role inheritance rule exists.
|
||||
func (e *SyncedEnforcer) HasGroupingPolicy(params ...interface{}) bool {
|
||||
func (e *SyncedEnforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.HasGroupingPolicy(params...)
|
||||
}
|
||||
|
||||
// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.
|
||||
func (e *SyncedEnforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) bool {
|
||||
func (e *SyncedEnforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.HasNamedGroupingPolicy(ptype, params...)
|
||||
|
||||
10
vendor/github.com/casbin/casbin/v2/frontend.go
generated
vendored
10
vendor/github.com/casbin/casbin/v2/frontend.go
generated
vendored
@ -27,7 +27,10 @@ func CasbinJsGetPermissionForUser(e IEnforcer, user string) (string, error) {
|
||||
|
||||
pRules := [][]string{}
|
||||
for ptype := range model["p"] {
|
||||
policies := model.GetPolicy("p", ptype)
|
||||
policies, err := model.GetPolicy("p", ptype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, rules := range policies {
|
||||
pRules = append(pRules, append([]string{ptype}, rules...))
|
||||
}
|
||||
@ -36,7 +39,10 @@ func CasbinJsGetPermissionForUser(e IEnforcer, user string) (string, error) {
|
||||
|
||||
gRules := [][]string{}
|
||||
for ptype := range model["g"] {
|
||||
policies := model.GetPolicy("g", ptype)
|
||||
policies, err := model.GetPolicy("g", ptype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, rules := range policies {
|
||||
gRules = append(gRules, append([]string{ptype}, rules...))
|
||||
}
|
||||
|
||||
72
vendor/github.com/casbin/casbin/v2/internal_api.go
generated
vendored
72
vendor/github.com/casbin/casbin/v2/internal_api.go
generated
vendored
@ -40,19 +40,23 @@ func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype string, rule []strin
|
||||
return true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})
|
||||
}
|
||||
|
||||
if e.model.HasPolicy(sec, ptype, rule) {
|
||||
return false, nil
|
||||
hasPolicy, err := e.model.HasPolicy(sec, ptype, rule)
|
||||
if hasPolicy || err != nil {
|
||||
return hasPolicy, err
|
||||
}
|
||||
|
||||
if e.shouldPersist() {
|
||||
if err := e.adapter.AddPolicy(sec, ptype, rule); err != nil {
|
||||
if err = e.adapter.AddPolicy(sec, ptype, rule); err != nil {
|
||||
if err.Error() != notImplemented {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.model.AddPolicy(sec, ptype, rule)
|
||||
err = e.model.AddPolicy(sec, ptype, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})
|
||||
@ -72,8 +76,11 @@ func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]
|
||||
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
|
||||
}
|
||||
|
||||
if !autoRemoveRepeat && e.model.HasPolicies(sec, ptype, rules) {
|
||||
return false, nil
|
||||
if !autoRemoveRepeat {
|
||||
hasPolicies, err := e.model.HasPolicies(sec, ptype, rules)
|
||||
if hasPolicies || err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if e.shouldPersist() {
|
||||
@ -84,7 +91,10 @@ func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]
|
||||
}
|
||||
}
|
||||
|
||||
e.model.AddPolicies(sec, ptype, rules)
|
||||
err := e.model.AddPolicies(sec, ptype, rules)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)
|
||||
@ -115,9 +125,9 @@ func (e *Enforcer) removePolicyWithoutNotify(sec string, ptype string, rule []st
|
||||
}
|
||||
}
|
||||
|
||||
ruleRemoved := e.model.RemovePolicy(sec, ptype, rule)
|
||||
if !ruleRemoved {
|
||||
return ruleRemoved, nil
|
||||
ruleRemoved, err := e.model.RemovePolicy(sec, ptype, rule)
|
||||
if !ruleRemoved || err != nil {
|
||||
return ruleRemoved, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -142,9 +152,9 @@ func (e *Enforcer) updatePolicyWithoutNotify(sec string, ptype string, oldRule [
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleUpdated := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
if !ruleUpdated {
|
||||
return ruleUpdated, nil
|
||||
ruleUpdated, err := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)
|
||||
if !ruleUpdated || err != nil {
|
||||
return ruleUpdated, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -178,9 +188,9 @@ func (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string, oldRule
|
||||
}
|
||||
}
|
||||
|
||||
ruleUpdated := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
if !ruleUpdated {
|
||||
return ruleUpdated, nil
|
||||
ruleUpdated, err := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)
|
||||
if !ruleUpdated || err != nil {
|
||||
return ruleUpdated, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -199,8 +209,8 @@ func (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string, oldRule
|
||||
|
||||
// removePolicies removes rules from the current policy.
|
||||
func (e *Enforcer) removePoliciesWithoutNotify(sec string, ptype string, rules [][]string) (bool, error) {
|
||||
if !e.model.HasPolicies(sec, ptype, rules) {
|
||||
return false, nil
|
||||
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules); !hasPolicies || err != nil {
|
||||
return hasPolicies, err
|
||||
}
|
||||
|
||||
if e.dispatcher != nil && e.autoNotifyDispatcher {
|
||||
@ -215,9 +225,9 @@ func (e *Enforcer) removePoliciesWithoutNotify(sec string, ptype string, rules [
|
||||
}
|
||||
}
|
||||
|
||||
rulesRemoved := e.model.RemovePolicies(sec, ptype, rules)
|
||||
if !rulesRemoved {
|
||||
return rulesRemoved, nil
|
||||
rulesRemoved, err := e.model.RemovePolicies(sec, ptype, rules)
|
||||
if !rulesRemoved || err != nil {
|
||||
return rulesRemoved, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -247,9 +257,9 @@ func (e *Enforcer) removeFilteredPolicyWithoutNotify(sec string, ptype string, f
|
||||
}
|
||||
}
|
||||
|
||||
ruleRemoved, effects := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
|
||||
if !ruleRemoved {
|
||||
return ruleRemoved, nil
|
||||
ruleRemoved, effects, err := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
|
||||
if !ruleRemoved || err != nil {
|
||||
return ruleRemoved, err
|
||||
}
|
||||
|
||||
if sec == "g" {
|
||||
@ -268,6 +278,10 @@ func (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec string, ptype string,
|
||||
err error
|
||||
)
|
||||
|
||||
if _, err = e.model.GetAssertion(sec, ptype); err != nil {
|
||||
return oldRules, err
|
||||
}
|
||||
|
||||
if e.shouldPersist() {
|
||||
if oldRules, err = e.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {
|
||||
if err.Error() != notImplemented {
|
||||
@ -286,8 +300,14 @@ func (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec string, ptype string,
|
||||
return oldRules, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)
|
||||
}
|
||||
|
||||
ruleChanged := e.model.RemovePolicies(sec, ptype, oldRules)
|
||||
e.model.AddPolicies(sec, ptype, newRules)
|
||||
ruleChanged, err := e.model.RemovePolicies(sec, ptype, oldRules)
|
||||
if err != nil {
|
||||
return oldRules, err
|
||||
}
|
||||
err = e.model.AddPolicies(sec, ptype, newRules)
|
||||
if err != nil {
|
||||
return oldRules, err
|
||||
}
|
||||
ruleChanged = ruleChanged && len(newRules) != 0
|
||||
if !ruleChanged {
|
||||
return make([][]string, 0), nil
|
||||
|
||||
40
vendor/github.com/casbin/casbin/v2/management_api.go
generated
vendored
40
vendor/github.com/casbin/casbin/v2/management_api.go
generated
vendored
@ -24,82 +24,82 @@ import (
|
||||
)
|
||||
|
||||
// GetAllSubjects gets the list of subjects that show up in the current policy.
|
||||
func (e *Enforcer) GetAllSubjects() []string {
|
||||
func (e *Enforcer) GetAllSubjects() ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicyAllTypes("p", 0)
|
||||
}
|
||||
|
||||
// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.
|
||||
func (e *Enforcer) GetAllNamedSubjects(ptype string) []string {
|
||||
func (e *Enforcer) GetAllNamedSubjects(ptype string) ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicy("p", ptype, 0)
|
||||
}
|
||||
|
||||
// GetAllObjects gets the list of objects that show up in the current policy.
|
||||
func (e *Enforcer) GetAllObjects() []string {
|
||||
func (e *Enforcer) GetAllObjects() ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicyAllTypes("p", 1)
|
||||
}
|
||||
|
||||
// GetAllNamedObjects gets the list of objects that show up in the current named policy.
|
||||
func (e *Enforcer) GetAllNamedObjects(ptype string) []string {
|
||||
func (e *Enforcer) GetAllNamedObjects(ptype string) ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicy("p", ptype, 1)
|
||||
}
|
||||
|
||||
// GetAllActions gets the list of actions that show up in the current policy.
|
||||
func (e *Enforcer) GetAllActions() []string {
|
||||
func (e *Enforcer) GetAllActions() ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicyAllTypes("p", 2)
|
||||
}
|
||||
|
||||
// GetAllNamedActions gets the list of actions that show up in the current named policy.
|
||||
func (e *Enforcer) GetAllNamedActions(ptype string) []string {
|
||||
func (e *Enforcer) GetAllNamedActions(ptype string) ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicy("p", ptype, 2)
|
||||
}
|
||||
|
||||
// GetAllRoles gets the list of roles that show up in the current policy.
|
||||
func (e *Enforcer) GetAllRoles() []string {
|
||||
func (e *Enforcer) GetAllRoles() ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicyAllTypes("g", 1)
|
||||
}
|
||||
|
||||
// GetAllNamedRoles gets the list of roles that show up in the current named policy.
|
||||
func (e *Enforcer) GetAllNamedRoles(ptype string) []string {
|
||||
func (e *Enforcer) GetAllNamedRoles(ptype string) ([]string, error) {
|
||||
return e.model.GetValuesForFieldInPolicy("g", ptype, 1)
|
||||
}
|
||||
|
||||
// GetPolicy gets all the authorization rules in the policy.
|
||||
func (e *Enforcer) GetPolicy() [][]string {
|
||||
func (e *Enforcer) GetPolicy() ([][]string, error) {
|
||||
return e.GetNamedPolicy("p")
|
||||
}
|
||||
|
||||
// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.
|
||||
func (e *Enforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *Enforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
return e.GetFilteredNamedPolicy("p", fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetNamedPolicy gets all the authorization rules in the named policy.
|
||||
func (e *Enforcer) GetNamedPolicy(ptype string) [][]string {
|
||||
func (e *Enforcer) GetNamedPolicy(ptype string) ([][]string, error) {
|
||||
return e.model.GetPolicy("p", ptype)
|
||||
}
|
||||
|
||||
// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.
|
||||
func (e *Enforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *Enforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
return e.model.GetFilteredPolicy("p", ptype, fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetGroupingPolicy gets all the role inheritance rules in the policy.
|
||||
func (e *Enforcer) GetGroupingPolicy() [][]string {
|
||||
func (e *Enforcer) GetGroupingPolicy() ([][]string, error) {
|
||||
return e.GetNamedGroupingPolicy("g")
|
||||
}
|
||||
|
||||
// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
|
||||
func (e *Enforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *Enforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
return e.GetFilteredNamedGroupingPolicy("g", fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.
|
||||
func (e *Enforcer) GetNamedGroupingPolicy(ptype string) [][]string {
|
||||
func (e *Enforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {
|
||||
return e.model.GetPolicy("g", ptype)
|
||||
}
|
||||
|
||||
// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
|
||||
func (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
return e.model.GetFilteredPolicy("g", ptype, fieldIndex, fieldValues...)
|
||||
}
|
||||
|
||||
@ -182,12 +182,12 @@ func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher strin
|
||||
}
|
||||
|
||||
// HasPolicy determines whether an authorization rule exists.
|
||||
func (e *Enforcer) HasPolicy(params ...interface{}) bool {
|
||||
func (e *Enforcer) HasPolicy(params ...interface{}) (bool, error) {
|
||||
return e.HasNamedPolicy("p", params...)
|
||||
}
|
||||
|
||||
// HasNamedPolicy determines whether a named authorization rule exists.
|
||||
func (e *Enforcer) HasNamedPolicy(ptype string, params ...interface{}) bool {
|
||||
func (e *Enforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {
|
||||
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
|
||||
return e.model.HasPolicy("p", ptype, strSlice)
|
||||
}
|
||||
@ -316,12 +316,12 @@ func (e *Enforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, field
|
||||
}
|
||||
|
||||
// HasGroupingPolicy determines whether a role inheritance rule exists.
|
||||
func (e *Enforcer) HasGroupingPolicy(params ...interface{}) bool {
|
||||
func (e *Enforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {
|
||||
return e.HasNamedGroupingPolicy("g", params...)
|
||||
}
|
||||
|
||||
// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.
|
||||
func (e *Enforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) bool {
|
||||
func (e *Enforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
|
||||
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
|
||||
return e.model.HasPolicy("g", ptype, strSlice)
|
||||
}
|
||||
|
||||
16
vendor/github.com/casbin/casbin/v2/model/model.go
generated
vendored
16
vendor/github.com/casbin/casbin/v2/model/model.go
generated
vendored
@ -212,6 +212,16 @@ func (model Model) hasSection(sec string) bool {
|
||||
return section != nil
|
||||
}
|
||||
|
||||
func (model Model) GetAssertion(sec string, ptype string) (*Assertion, error) {
|
||||
if model[sec] == nil {
|
||||
return nil, fmt.Errorf("missing required section %s", sec)
|
||||
}
|
||||
if model[sec][ptype] == nil {
|
||||
return nil, fmt.Errorf("missiong required definition %s in section %s", ptype, sec)
|
||||
}
|
||||
return model[sec][ptype], nil
|
||||
}
|
||||
|
||||
// PrintModel prints the model to the log.
|
||||
func (model Model) PrintModel() {
|
||||
if !model.GetLogger().IsEnabled() {
|
||||
@ -236,6 +246,10 @@ func (model Model) SortPoliciesBySubjectHierarchy() error {
|
||||
if model["e"]["e"].Value != constant.SubjectPriorityEffect {
|
||||
return nil
|
||||
}
|
||||
g, err := model.GetAssertion("g", "g")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subIndex := 0
|
||||
for ptype, assertion := range model["p"] {
|
||||
domainIndex, err := model.GetFieldIndex(ptype, constant.DomainIndex)
|
||||
@ -243,7 +257,7 @@ func (model Model) SortPoliciesBySubjectHierarchy() error {
|
||||
domainIndex = -1
|
||||
}
|
||||
policies := assertion.Policy
|
||||
subjectHierarchyMap, err := getSubjectHierarchyMap(model["g"]["g"].Policy)
|
||||
subjectHierarchyMap, err := getSubjectHierarchyMap(g.Policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
148
vendor/github.com/casbin/casbin/v2/model/policy.go
generated
vendored
148
vendor/github.com/casbin/casbin/v2/model/policy.go
generated
vendored
@ -38,6 +38,10 @@ const DefaultSep = ","
|
||||
// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
|
||||
func (model Model) BuildIncrementalRoleLinks(rmMap map[string]rbac.RoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
|
||||
if sec == "g" && rmMap[ptype] != nil {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model[sec][ptype].buildIncrementalRoleLinks(rmMap[ptype], op, rules)
|
||||
}
|
||||
return nil
|
||||
@ -61,6 +65,10 @@ func (model Model) BuildRoleLinks(rmMap map[string]rbac.RoleManager) error {
|
||||
// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.
|
||||
func (model Model) BuildIncrementalConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
|
||||
if sec == "g" && condRmMap[ptype] != nil {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model[sec][ptype].buildIncrementalConditionalRoleLinks(condRmMap[ptype], op, rules)
|
||||
}
|
||||
return nil
|
||||
@ -126,12 +134,20 @@ func (model Model) ClearPolicy() {
|
||||
}
|
||||
|
||||
// GetPolicy gets all rules in a policy.
|
||||
func (model Model) GetPolicy(sec string, ptype string) [][]string {
|
||||
return model[sec][ptype].Policy
|
||||
func (model Model) GetPolicy(sec string, ptype string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model[sec][ptype].Policy, nil
|
||||
}
|
||||
|
||||
// GetFilteredPolicy gets rules based on field filters from a policy.
|
||||
func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) [][]string {
|
||||
func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := [][]string{}
|
||||
|
||||
for _, rule := range model[sec][ptype].Policy {
|
||||
@ -148,12 +164,15 @@ func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, f
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// HasPolicyEx determines whether a model has the specified policy rule with error.
|
||||
func (model Model) HasPolicyEx(sec string, ptype string, rule []string) (bool, error) {
|
||||
assertion := model[sec][ptype]
|
||||
assertion, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch sec {
|
||||
case "p":
|
||||
if len(rule) != len(assertion.Tokens) {
|
||||
@ -172,29 +191,40 @@ func (model Model) HasPolicyEx(sec string, ptype string, rule []string) (bool, e
|
||||
rule)
|
||||
}
|
||||
}
|
||||
return model.HasPolicy(sec, ptype, rule), nil
|
||||
return model.HasPolicy(sec, ptype, rule)
|
||||
}
|
||||
|
||||
// HasPolicy determines whether a model has the specified policy rule.
|
||||
func (model Model) HasPolicy(sec string, ptype string, rule []string) bool {
|
||||
func (model Model) HasPolicy(sec string, ptype string, rule []string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
|
||||
return ok
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// HasPolicies determines whether a model has any of the specified policies. If one is found we return true.
|
||||
func (model Model) HasPolicies(sec string, ptype string, rules [][]string) bool {
|
||||
func (model Model) HasPolicies(sec string, ptype string, rules [][]string) (bool, error) {
|
||||
for i := 0; i < len(rules); i++ {
|
||||
if model.HasPolicy(sec, ptype, rules[i]) {
|
||||
return true
|
||||
ok, err := model.HasPolicy(sec, ptype, rules[i])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddPolicy adds a policy rule to the model.
|
||||
func (model Model) AddPolicy(sec string, ptype string, rule []string) {
|
||||
assertion := model[sec][ptype]
|
||||
func (model Model) AddPolicy(sec string, ptype string, rule []string) error {
|
||||
assertion, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assertion.Policy = append(assertion.Policy, rule)
|
||||
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1
|
||||
|
||||
@ -217,15 +247,21 @@ func (model Model) AddPolicy(sec string, ptype string, rule []string) {
|
||||
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPolicies adds policy rules to the model.
|
||||
func (model Model) AddPolicies(sec string, ptype string, rules [][]string) {
|
||||
_ = model.AddPoliciesWithAffected(sec, ptype, rules)
|
||||
func (model Model) AddPolicies(sec string, ptype string, rules [][]string) error {
|
||||
_, err := model.AddPoliciesWithAffected(sec, ptype, rules)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddPoliciesWithAffected adds policy rules to the model, and returns affected rules.
|
||||
func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]string) [][]string {
|
||||
func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var affected [][]string
|
||||
for _, rule := range rules {
|
||||
hashKey := strings.Join(rule, DefaultSep)
|
||||
@ -234,17 +270,24 @@ func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]s
|
||||
continue
|
||||
}
|
||||
affected = append(affected, rule)
|
||||
model.AddPolicy(sec, ptype, rule)
|
||||
err = model.AddPolicy(sec, ptype, rule)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
}
|
||||
return affected
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// RemovePolicy removes a policy rule from the model.
|
||||
// Deprecated: Using AddPoliciesWithAffected instead.
|
||||
func (model Model) RemovePolicy(sec string, ptype string, rule []string) bool {
|
||||
func (model Model) RemovePolicy(sec string, ptype string, rule []string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
index, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
|
||||
if !ok {
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
|
||||
model[sec][ptype].Policy = append(model[sec][ptype].Policy[:index], model[sec][ptype].Policy[index+1:]...)
|
||||
@ -253,26 +296,34 @@ func (model Model) RemovePolicy(sec string, ptype string, rule []string) bool {
|
||||
model[sec][ptype].PolicyMap[strings.Join(model[sec][ptype].Policy[i], DefaultSep)] = i
|
||||
}
|
||||
|
||||
return true
|
||||
return true, err
|
||||
}
|
||||
|
||||
// UpdatePolicy updates a policy rule from the model.
|
||||
func (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newRule []string) bool {
|
||||
func (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
oldPolicy := strings.Join(oldRule, DefaultSep)
|
||||
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
|
||||
if !ok {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
model[sec][ptype].Policy[index] = newRule
|
||||
delete(model[sec][ptype].PolicyMap, oldPolicy)
|
||||
model[sec][ptype].PolicyMap[strings.Join(newRule, DefaultSep)] = index
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UpdatePolicies updates a policy rule from the model.
|
||||
func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) bool {
|
||||
func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rollbackFlag := false
|
||||
// index -> []{oldIndex, newIndex}
|
||||
modifiedRuleIndex := make(map[int][]int)
|
||||
@ -295,7 +346,7 @@ func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [
|
||||
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
|
||||
if !ok {
|
||||
rollbackFlag = true
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
model[sec][ptype].Policy[index] = newRules[newIndex]
|
||||
@ -305,17 +356,21 @@ func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [
|
||||
newIndex++
|
||||
}
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RemovePolicies removes policy rules from the model.
|
||||
func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) bool {
|
||||
affected := model.RemovePoliciesWithAffected(sec, ptype, rules)
|
||||
return len(affected) != 0
|
||||
func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {
|
||||
affected, err := model.RemovePoliciesWithAffected(sec, ptype, rules)
|
||||
return len(affected) != 0, err
|
||||
}
|
||||
|
||||
// RemovePoliciesWithAffected removes policy rules from the model, and returns affected rules.
|
||||
func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) [][]string {
|
||||
func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var affected [][]string
|
||||
for _, rule := range rules {
|
||||
index, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
|
||||
@ -330,11 +385,15 @@ func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules []
|
||||
model[sec][ptype].PolicyMap[strings.Join(model[sec][ptype].Policy[i], DefaultSep)] = i
|
||||
}
|
||||
}
|
||||
return affected
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
// RemoveFilteredPolicy removes policy rules based on field filters from the model.
|
||||
func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string) {
|
||||
func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var tmp [][]string
|
||||
var effects [][]string
|
||||
res := false
|
||||
@ -362,31 +421,40 @@ func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int
|
||||
res = true
|
||||
}
|
||||
|
||||
return res, effects
|
||||
return res, effects, nil
|
||||
}
|
||||
|
||||
// GetValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed.
|
||||
func (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) []string {
|
||||
func (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) ([]string, error) {
|
||||
values := []string{}
|
||||
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rule := range model[sec][ptype].Policy {
|
||||
values = append(values, rule[fieldIndex])
|
||||
}
|
||||
|
||||
util.ArrayRemoveDuplicates(&values)
|
||||
|
||||
return values
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
|
||||
func (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) []string {
|
||||
func (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) ([]string, error) {
|
||||
values := []string{}
|
||||
|
||||
for ptype := range model[sec] {
|
||||
values = append(values, model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)...)
|
||||
v, err := model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, v...)
|
||||
}
|
||||
|
||||
util.ArrayRemoveDuplicates(&values)
|
||||
|
||||
return values
|
||||
return values, nil
|
||||
}
|
||||
|
||||
39
vendor/github.com/casbin/casbin/v2/rbac_api.go
generated
vendored
39
vendor/github.com/casbin/casbin/v2/rbac_api.go
generated
vendored
@ -199,20 +199,24 @@ func (e *Enforcer) GetNamedPermissionsForUser(ptype string, user string, domain
|
||||
args[subIndex] = user
|
||||
|
||||
if len(domain) > 0 {
|
||||
index, err := e.GetFieldIndex(ptype, constant.DomainIndex)
|
||||
var index int
|
||||
index, err = e.GetFieldIndex(ptype, constant.DomainIndex)
|
||||
if err != nil {
|
||||
return permission, err
|
||||
}
|
||||
args[index] = domain[0]
|
||||
}
|
||||
perm := e.GetFilteredNamedPolicy(ptype, 0, args...)
|
||||
perm, err := e.GetFilteredNamedPolicy(ptype, 0, args...)
|
||||
if err != nil {
|
||||
return permission, err
|
||||
}
|
||||
permission = append(permission, perm...)
|
||||
}
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
// HasPermissionForUser determines whether a user has a permission.
|
||||
func (e *Enforcer) HasPermissionForUser(user string, permission ...string) bool {
|
||||
func (e *Enforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {
|
||||
return e.HasPolicy(util.JoinSlice(user, permission...))
|
||||
}
|
||||
|
||||
@ -349,9 +353,18 @@ func (e *Enforcer) GetNamedImplicitPermissionsForUser(ptype string, user string,
|
||||
// GetImplicitUsersForPermission("data1", "read") will get: ["alice", "bob"].
|
||||
// Note: only users will be returned, roles (2nd arg in "g") will be excluded.
|
||||
func (e *Enforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) {
|
||||
pSubjects := e.GetAllSubjects()
|
||||
gInherit := e.model.GetValuesForFieldInPolicyAllTypes("g", 1)
|
||||
gSubjects := e.model.GetValuesForFieldInPolicyAllTypes("g", 0)
|
||||
pSubjects, err := e.GetAllSubjects()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gInherit, err := e.model.GetValuesForFieldInPolicyAllTypes("g", 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gSubjects, err := e.model.GetValuesForFieldInPolicyAllTypes("g", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subjects := append(pSubjects, gSubjects...)
|
||||
util.ArrayRemoveDuplicates(&subjects)
|
||||
@ -504,7 +517,11 @@ func (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, err
|
||||
}
|
||||
|
||||
isRole := make(map[string]bool)
|
||||
for _, role := range e.GetAllRoles() {
|
||||
roles, err := e.GetAllRoles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, role := range roles {
|
||||
isRole[role] = true
|
||||
}
|
||||
|
||||
@ -550,8 +567,12 @@ func (e *Enforcer) GetImplicitUsersForResourceByDomain(resource string, domain s
|
||||
|
||||
isRole := make(map[string]bool)
|
||||
|
||||
for _, role := range e.GetAllRolesByDomain(domain) {
|
||||
isRole[role] = true
|
||||
if roles, err := e.GetAllRolesByDomain(domain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, role := range roles {
|
||||
isRole[role] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range e.model["p"]["p"].Policy {
|
||||
|
||||
2
vendor/github.com/casbin/casbin/v2/rbac_api_synced.go
generated
vendored
2
vendor/github.com/casbin/casbin/v2/rbac_api_synced.go
generated
vendored
@ -138,7 +138,7 @@ func (e *SyncedEnforcer) GetNamedPermissionsForUser(ptype string, user string, d
|
||||
}
|
||||
|
||||
// HasPermissionForUser determines whether a user has a permission.
|
||||
func (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) bool {
|
||||
func (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {
|
||||
e.m.RLock()
|
||||
defer e.m.RUnlock()
|
||||
return e.Enforcer.HasPermissionForUser(user, permission...)
|
||||
|
||||
29
vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go
generated
vendored
29
vendor/github.com/casbin/casbin/v2/rbac_api_with_domains.go
generated
vendored
@ -76,14 +76,17 @@ func (e *Enforcer) DeleteRolesForUserInDomain(user string, domain string) (bool,
|
||||
}
|
||||
|
||||
// GetAllUsersByDomain would get all users associated with the domain.
|
||||
func (e *Enforcer) GetAllUsersByDomain(domain string) []string {
|
||||
func (e *Enforcer) GetAllUsersByDomain(domain string) ([]string, error) {
|
||||
m := make(map[string]struct{})
|
||||
g := e.model["g"]["g"]
|
||||
g, err := e.model.GetAssertion("g", "g")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
p := e.model["p"]["p"]
|
||||
users := make([]string, 0)
|
||||
index, err := e.GetFieldIndex("p", constant.DomainIndex)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
getUser := func(index int, policies [][]string, domain string, m map[string]struct{}) []string {
|
||||
@ -102,12 +105,15 @@ func (e *Enforcer) GetAllUsersByDomain(domain string) []string {
|
||||
|
||||
users = append(users, getUser(2, g.Policy, domain, m)...)
|
||||
users = append(users, getUser(index, p.Policy, domain, m)...)
|
||||
return users
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// DeleteAllUsersByDomain would delete all users associated with the domain.
|
||||
func (e *Enforcer) DeleteAllUsersByDomain(domain string) (bool, error) {
|
||||
g := e.model["g"]["g"]
|
||||
g, err := e.model.GetAssertion("g", "g")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
p := e.model["p"]["p"]
|
||||
index, err := e.GetFieldIndex("p", constant.DomainIndex)
|
||||
if err != nil {
|
||||
@ -128,11 +134,11 @@ func (e *Enforcer) DeleteAllUsersByDomain(domain string) (bool, error) {
|
||||
}
|
||||
|
||||
users := getUser(2, g.Policy, domain)
|
||||
if _, err := e.RemoveGroupingPolicies(users); err != nil {
|
||||
if _, err = e.RemoveGroupingPolicies(users); err != nil {
|
||||
return false, err
|
||||
}
|
||||
users = getUser(index, p.Policy, domain)
|
||||
if _, err := e.RemovePolicies(users); err != nil {
|
||||
if _, err = e.RemovePolicies(users); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@ -163,8 +169,11 @@ func (e *Enforcer) GetAllDomains() ([]string, error) {
|
||||
|
||||
// GetAllRolesByDomain would get all roles associated with the domain.
|
||||
// note: Not applicable to Domains with inheritance relationship (implicit roles)
|
||||
func (e *Enforcer) GetAllRolesByDomain(domain string) []string {
|
||||
g := e.model["g"]["g"]
|
||||
func (e *Enforcer) GetAllRolesByDomain(domain string) ([]string, error) {
|
||||
g, err := e.model.GetAssertion("g", "g")
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
policies := g.Policy
|
||||
roles := make([]string, 0)
|
||||
existMap := make(map[string]bool) // remove duplicates
|
||||
@ -179,5 +188,5 @@ func (e *Enforcer) GetAllRolesByDomain(domain string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
return roles
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
6
vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
generated
vendored
6
vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
generated
vendored
@ -9,6 +9,8 @@ func Render(doc []byte) []byte {
|
||||
renderer := NewRoffRenderer()
|
||||
|
||||
return blackfriday.Run(doc,
|
||||
[]blackfriday.Option{blackfriday.WithRenderer(renderer),
|
||||
blackfriday.WithExtensions(renderer.GetExtensions())}...)
|
||||
[]blackfriday.Option{
|
||||
blackfriday.WithRenderer(renderer),
|
||||
blackfriday.WithExtensions(renderer.GetExtensions()),
|
||||
}...)
|
||||
}
|
||||
|
||||
114
vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
generated
vendored
114
vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
generated
vendored
@ -1,6 +1,8 @@
|
||||
package md2man
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -20,34 +22,35 @@ type roffRenderer struct {
|
||||
}
|
||||
|
||||
const (
|
||||
titleHeader = ".TH "
|
||||
topLevelHeader = "\n\n.SH "
|
||||
secondLevelHdr = "\n.SH "
|
||||
otherHeader = "\n.SS "
|
||||
crTag = "\n"
|
||||
emphTag = "\\fI"
|
||||
emphCloseTag = "\\fP"
|
||||
strongTag = "\\fB"
|
||||
strongCloseTag = "\\fP"
|
||||
breakTag = "\n.br\n"
|
||||
paraTag = "\n.PP\n"
|
||||
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
|
||||
linkTag = "\n\\[la]"
|
||||
linkCloseTag = "\\[ra]"
|
||||
codespanTag = "\\fB\\fC"
|
||||
codespanCloseTag = "\\fR"
|
||||
codeTag = "\n.PP\n.RS\n\n.nf\n"
|
||||
codeCloseTag = "\n.fi\n.RE\n"
|
||||
quoteTag = "\n.PP\n.RS\n"
|
||||
quoteCloseTag = "\n.RE\n"
|
||||
listTag = "\n.RS\n"
|
||||
listCloseTag = "\n.RE\n"
|
||||
dtTag = "\n.TP\n"
|
||||
dd2Tag = "\n"
|
||||
tableStart = "\n.TS\nallbox;\n"
|
||||
tableEnd = ".TE\n"
|
||||
tableCellStart = "T{\n"
|
||||
tableCellEnd = "\nT}\n"
|
||||
titleHeader = ".TH "
|
||||
topLevelHeader = "\n\n.SH "
|
||||
secondLevelHdr = "\n.SH "
|
||||
otherHeader = "\n.SS "
|
||||
crTag = "\n"
|
||||
emphTag = "\\fI"
|
||||
emphCloseTag = "\\fP"
|
||||
strongTag = "\\fB"
|
||||
strongCloseTag = "\\fP"
|
||||
breakTag = "\n.br\n"
|
||||
paraTag = "\n.PP\n"
|
||||
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
|
||||
linkTag = "\n\\[la]"
|
||||
linkCloseTag = "\\[ra]"
|
||||
codespanTag = "\\fB"
|
||||
codespanCloseTag = "\\fR"
|
||||
codeTag = "\n.EX\n"
|
||||
codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on).
|
||||
quoteTag = "\n.PP\n.RS\n"
|
||||
quoteCloseTag = "\n.RE\n"
|
||||
listTag = "\n.RS\n"
|
||||
listCloseTag = "\n.RE\n"
|
||||
dtTag = "\n.TP\n"
|
||||
dd2Tag = "\n"
|
||||
tableStart = "\n.TS\nallbox;\n"
|
||||
tableEnd = ".TE\n"
|
||||
tableCellStart = "T{\n"
|
||||
tableCellEnd = "\nT}\n"
|
||||
tablePreprocessor = `'\" t`
|
||||
)
|
||||
|
||||
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
|
||||
@ -74,6 +77,16 @@ func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
|
||||
|
||||
// RenderHeader handles outputting the header at document start
|
||||
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
|
||||
// We need to walk the tree to check if there are any tables.
|
||||
// If there are, we need to enable the roff table preprocessor.
|
||||
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
if node.Type == blackfriday.Table {
|
||||
out(w, tablePreprocessor+"\n")
|
||||
return blackfriday.Terminate
|
||||
}
|
||||
return blackfriday.GoToNext
|
||||
})
|
||||
|
||||
// disable hyphenation
|
||||
out(w, ".nh\n")
|
||||
}
|
||||
@ -86,8 +99,7 @@ func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
|
||||
// RenderNode is called for each node in a markdown document; based on the node
|
||||
// type the equivalent roff output is sent to the writer
|
||||
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
|
||||
var walkAction = blackfriday.GoToNext
|
||||
walkAction := blackfriday.GoToNext
|
||||
|
||||
switch node.Type {
|
||||
case blackfriday.Text:
|
||||
@ -109,9 +121,16 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
|
||||
out(w, strongCloseTag)
|
||||
}
|
||||
case blackfriday.Link:
|
||||
if !entering {
|
||||
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
|
||||
// Don't render the link text for automatic links, because this
|
||||
// will only duplicate the URL in the roff output.
|
||||
// See https://daringfireball.net/projects/markdown/syntax#autolink
|
||||
if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
|
||||
out(w, string(node.FirstChild.Literal))
|
||||
}
|
||||
// Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
|
||||
escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
|
||||
out(w, linkTag+escapedLink+linkCloseTag)
|
||||
walkAction = blackfriday.SkipChildren
|
||||
case blackfriday.Image:
|
||||
// ignore images
|
||||
walkAction = blackfriday.SkipChildren
|
||||
@ -160,6 +179,11 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
|
||||
r.handleTableCell(w, node, entering)
|
||||
case blackfriday.HTMLSpan:
|
||||
// ignore other HTML tags
|
||||
case blackfriday.HTMLBlock:
|
||||
if bytes.HasPrefix(node.Literal, []byte("<!--")) {
|
||||
break // ignore comments, no warning
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
}
|
||||
@ -254,7 +278,7 @@ func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, ente
|
||||
start = "\t"
|
||||
}
|
||||
if node.IsHeader {
|
||||
start += codespanTag
|
||||
start += strongTag
|
||||
} else if nodeLiteralSize(node) > 30 {
|
||||
start += tableCellStart
|
||||
}
|
||||
@ -262,7 +286,7 @@ func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, ente
|
||||
} else {
|
||||
var end string
|
||||
if node.IsHeader {
|
||||
end = codespanCloseTag
|
||||
end = strongCloseTag
|
||||
} else if nodeLiteralSize(node) > 30 {
|
||||
end = tableCellEnd
|
||||
}
|
||||
@ -310,6 +334,28 @@ func out(w io.Writer, output string) {
|
||||
}
|
||||
|
||||
func escapeSpecialChars(w io.Writer, text []byte) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(text))
|
||||
|
||||
// count the number of lines in the text
|
||||
// we need to know this to avoid adding a newline after the last line
|
||||
n := bytes.Count(text, []byte{'\n'})
|
||||
idx := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
dt := scanner.Bytes()
|
||||
if idx < n {
|
||||
idx++
|
||||
dt = append(dt, '\n')
|
||||
}
|
||||
escapeSpecialCharsLine(w, dt)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func escapeSpecialCharsLine(w io.Writer, text []byte) {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// escape initial apostrophe or period
|
||||
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
|
||||
|
||||
30
vendor/github.com/datarhei/joy4/format/flv/flv.go
generated
vendored
30
vendor/github.com/datarhei/joy4/format/flv/flv.go
generated
vendored
@ -81,10 +81,19 @@ type Prober struct {
|
||||
GotAudio, GotVideo bool
|
||||
VideoStreamIdx, AudioStreamIdx int
|
||||
PushedCount int
|
||||
MaxProbePacketCount int
|
||||
Streams []av.CodecData
|
||||
CachedPkts []av.Packet
|
||||
}
|
||||
|
||||
func NewProber(maxProbePacketCount int) *Prober {
|
||||
prober := &Prober{
|
||||
MaxProbePacketCount: maxProbePacketCount,
|
||||
}
|
||||
|
||||
return prober
|
||||
}
|
||||
|
||||
func (prober *Prober) CacheTag(_tag flvio.Tag, timestamp int32) {
|
||||
pkt, _ := prober.TagToPacket(_tag, timestamp)
|
||||
prober.CachedPkts = append(prober.CachedPkts, pkt)
|
||||
@ -93,7 +102,11 @@ func (prober *Prober) CacheTag(_tag flvio.Tag, timestamp int32) {
|
||||
func (prober *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
||||
prober.PushedCount++
|
||||
|
||||
if prober.PushedCount > MaxProbePacketCount {
|
||||
if prober.MaxProbePacketCount <= 0 {
|
||||
prober.MaxProbePacketCount = MaxProbePacketCount
|
||||
}
|
||||
|
||||
if prober.PushedCount > prober.MaxProbePacketCount {
|
||||
err = fmt.Errorf("flv: max probe packet count reached")
|
||||
return
|
||||
}
|
||||
@ -229,16 +242,21 @@ func (prober *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
|
||||
}
|
||||
|
||||
func (prober *Prober) Probed() (ok bool) {
|
||||
if prober.MaxProbePacketCount <= 0 {
|
||||
prober.MaxProbePacketCount = MaxProbePacketCount
|
||||
}
|
||||
|
||||
if prober.HasAudio || prober.HasVideo {
|
||||
if prober.HasAudio == prober.GotAudio && prober.HasVideo == prober.GotVideo {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if prober.PushedCount == MaxProbePacketCount {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
if prober.PushedCount == prober.MaxProbePacketCount {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (prober *Prober) TagToPacket(tag flvio.Tag, timestamp int32) (pkt av.Packet, ok bool) {
|
||||
|
||||
186
vendor/github.com/datarhei/joy4/format/rtmp/rtmp.go
generated
vendored
186
vendor/github.com/datarhei/joy4/format/rtmp/rtmp.go
generated
vendored
@ -65,10 +65,33 @@ type Server struct {
|
||||
HandlePlay func(*Conn)
|
||||
HandleConn func(*Conn)
|
||||
|
||||
MaxProbePacketCount int
|
||||
SkipInvalidMessages bool
|
||||
DebugChunks func(conn net.Conn) bool
|
||||
|
||||
listener net.Listener
|
||||
doneChan chan struct{}
|
||||
}
|
||||
|
||||
func (s *Server) HandleNetConn(netconn net.Conn) (err error) {
|
||||
conn := NewConn(netconn)
|
||||
conn.prober = flv.NewProber(s.MaxProbePacketCount)
|
||||
conn.skipInvalidMessages = s.SkipInvalidMessages
|
||||
if s.DebugChunks != nil {
|
||||
conn.debugChunks = s.DebugChunks(netconn)
|
||||
}
|
||||
conn.isserver = true
|
||||
|
||||
err = s.handleConn(conn)
|
||||
if Debug {
|
||||
fmt.Println("rtmp: server: client closed err:", err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) handleConn(conn *Conn) (err error) {
|
||||
if s.HandleConn != nil {
|
||||
s.HandleConn(conn)
|
||||
@ -171,15 +194,12 @@ func (s *Server) Serve(listener net.Listener) error {
|
||||
fmt.Println("rtmp: server: accepted")
|
||||
}
|
||||
|
||||
conn := NewConn(netconn)
|
||||
conn.isserver = true
|
||||
go func() {
|
||||
err := s.handleConn(conn)
|
||||
go func(conn net.Conn) {
|
||||
err := s.HandleNetConn(conn)
|
||||
if Debug {
|
||||
fmt.Println("rtmp: server: client closed err:", err)
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
}(netconn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,6 +272,10 @@ type Conn struct {
|
||||
eventtype uint16
|
||||
|
||||
start time.Time
|
||||
|
||||
skipInvalidMessages bool
|
||||
|
||||
debugChunks bool
|
||||
}
|
||||
|
||||
type txrxcount struct {
|
||||
@ -279,6 +303,7 @@ func NewConn(netconn net.Conn) *Conn {
|
||||
conn.readcsmap = make(map[uint32]*chunkStream)
|
||||
conn.readMaxChunkSize = 128
|
||||
conn.writeMaxChunkSize = 128
|
||||
conn.readAckSize = 1048576
|
||||
conn.txrxcount = &txrxcount{ReadWriter: netconn}
|
||||
conn.bufr = bufio.NewReaderSize(conn.txrxcount, pio.RecommendBufioSize)
|
||||
conn.bufw = bufio.NewWriterSize(conn.txrxcount, pio.RecommendBufioSize)
|
||||
@ -295,12 +320,14 @@ type chunkStream struct {
|
||||
gentimenow bool
|
||||
timedelta uint32
|
||||
hastimeext bool
|
||||
timeext uint32
|
||||
msgsid uint32
|
||||
msgtypeid uint8
|
||||
msgdatalen uint32
|
||||
msgdataleft uint32
|
||||
msghdrtype uint8
|
||||
msgdata []byte
|
||||
msgcount int
|
||||
}
|
||||
|
||||
func (cs *chunkStream) Start() {
|
||||
@ -379,6 +406,15 @@ func (conn *Conn) pollMsg() (err error) {
|
||||
if err = conn.readChunk(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if conn.readAckSize != 0 && conn.ackn > conn.readAckSize {
|
||||
if err = conn.writeAck(conn.ackn, false); err != nil {
|
||||
return fmt.Errorf("writeACK: %w", err)
|
||||
}
|
||||
conn.flushWrite()
|
||||
conn.ackn = 0
|
||||
}
|
||||
|
||||
if conn.gotmsg {
|
||||
return
|
||||
}
|
||||
@ -757,7 +793,7 @@ func (conn *Conn) writeConnect(path string) (err error) {
|
||||
} else {
|
||||
if conn.msgtypeid == msgtypeidWindowAckSize {
|
||||
if len(conn.msgdata) == 4 {
|
||||
conn.readAckSize = pio.U32BE(conn.msgdata)
|
||||
conn.readAckSize = pio.U32BE(conn.msgdata) >> 1
|
||||
}
|
||||
//if err = self.writeWindowAckSize(0xffffffff); err != nil {
|
||||
// return
|
||||
@ -1004,14 +1040,10 @@ func (conn *Conn) WriteHeader(streams []av.CodecData) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
var metadata flvio.AMFMap = nil
|
||||
var metadata flvio.AMFMap
|
||||
|
||||
//metadata = self.GetMetaData()
|
||||
|
||||
if metadata == nil {
|
||||
if metadata, err = flv.NewMetadataByStreams(streams); err != nil {
|
||||
return
|
||||
}
|
||||
if metadata, err = flv.NewMetadataByStreams(streams); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// > onMetaData()
|
||||
@ -1345,10 +1377,17 @@ func (conn *Conn) readChunk() (err error) {
|
||||
csid = uint32(pio.U16BE(b)) + 64
|
||||
}
|
||||
|
||||
newcs := false
|
||||
cs := conn.readcsmap[csid]
|
||||
if cs == nil {
|
||||
cs = &chunkStream{}
|
||||
conn.readcsmap[csid] = cs
|
||||
newcs = true
|
||||
}
|
||||
|
||||
if len(conn.readcsmap) > 16 {
|
||||
err = fmt.Errorf("too many chunk stream ids")
|
||||
return
|
||||
}
|
||||
|
||||
var timestamp uint32
|
||||
@ -1367,8 +1406,10 @@ func (conn *Conn) readChunk() (err error) {
|
||||
//
|
||||
// Figure 9 Chunk Message Header – Type 0
|
||||
if cs.msgdataleft != 0 {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
return
|
||||
if !conn.skipInvalidMessages {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
return
|
||||
}
|
||||
}
|
||||
h := b[:11]
|
||||
if _, err = io.ReadFull(conn.bufr, h); err != nil {
|
||||
@ -1387,6 +1428,7 @@ func (conn *Conn) readChunk() (err error) {
|
||||
n += 4
|
||||
timestamp = pio.U32BE(b)
|
||||
cs.hastimeext = true
|
||||
cs.timeext = timestamp
|
||||
} else {
|
||||
cs.hastimeext = false
|
||||
}
|
||||
@ -1403,10 +1445,16 @@ func (conn *Conn) readChunk() (err error) {
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Figure 10 Chunk Message Header – Type 1
|
||||
if cs.msgdataleft != 0 {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
if newcs {
|
||||
err = fmt.Errorf("chunk message type 1 without previous chunk")
|
||||
return
|
||||
}
|
||||
if cs.msgdataleft != 0 {
|
||||
if !conn.skipInvalidMessages {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
return
|
||||
}
|
||||
}
|
||||
h := b[:7]
|
||||
if _, err = io.ReadFull(conn.bufr, h); err != nil {
|
||||
return
|
||||
@ -1423,6 +1471,7 @@ func (conn *Conn) readChunk() (err error) {
|
||||
n += 4
|
||||
timestamp = pio.U32BE(b)
|
||||
cs.hastimeext = true
|
||||
cs.timeext = timestamp
|
||||
} else {
|
||||
cs.hastimeext = false
|
||||
}
|
||||
@ -1438,10 +1487,16 @@ func (conn *Conn) readChunk() (err error) {
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Figure 11 Chunk Message Header – Type 2
|
||||
if cs.msgdataleft != 0 {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
if newcs {
|
||||
err = fmt.Errorf("chunk message type 2 without previous chunk")
|
||||
return
|
||||
}
|
||||
if cs.msgdataleft != 0 {
|
||||
if !conn.skipInvalidMessages {
|
||||
err = fmt.Errorf("chunk msgdataleft=%d invalid", cs.msgdataleft)
|
||||
return
|
||||
}
|
||||
}
|
||||
h := b[:3]
|
||||
if _, err = io.ReadFull(conn.bufr, h); err != nil {
|
||||
return
|
||||
@ -1456,6 +1511,7 @@ func (conn *Conn) readChunk() (err error) {
|
||||
n += 4
|
||||
timestamp = pio.U32BE(b)
|
||||
cs.hastimeext = true
|
||||
cs.timeext = timestamp
|
||||
} else {
|
||||
cs.hastimeext = false
|
||||
}
|
||||
@ -1464,6 +1520,11 @@ func (conn *Conn) readChunk() (err error) {
|
||||
cs.Start()
|
||||
|
||||
case 3:
|
||||
if newcs {
|
||||
err = fmt.Errorf("chunk message type 3 without previous chunk")
|
||||
return
|
||||
}
|
||||
|
||||
if cs.msgdataleft == 0 {
|
||||
switch cs.msghdrtype {
|
||||
case 0:
|
||||
@ -1474,6 +1535,7 @@ func (conn *Conn) readChunk() (err error) {
|
||||
n += 4
|
||||
timestamp = pio.U32BE(b)
|
||||
cs.timenow = timestamp
|
||||
cs.timeext = timestamp
|
||||
}
|
||||
case 1, 2:
|
||||
if cs.hastimeext {
|
||||
@ -1488,6 +1550,18 @@ func (conn *Conn) readChunk() (err error) {
|
||||
cs.timenow += timestamp
|
||||
}
|
||||
cs.Start()
|
||||
} else {
|
||||
if cs.hastimeext {
|
||||
var b []byte
|
||||
if b, err = conn.bufr.Peek(4); err != nil {
|
||||
return
|
||||
}
|
||||
if pio.U32BE(b) == cs.timeext {
|
||||
if _, err = io.ReadFull(conn.bufr, b[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@ -1508,8 +1582,27 @@ func (conn *Conn) readChunk() (err error) {
|
||||
cs.msgdataleft -= uint32(size)
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("rtmp: chunk msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d\n",
|
||||
cs.msgsid, cs.msgtypeid, cs.msghdrtype, cs.msgdatalen, cs.msgdataleft)
|
||||
fmt.Printf("rtmp: chunk msgsid=%d msgtypeid=%d msghdrtype=%d len=%d left=%d max=%d",
|
||||
cs.msgsid, cs.msgtypeid, msghdrtype, cs.msgdatalen, cs.msgdataleft, conn.readMaxChunkSize)
|
||||
}
|
||||
|
||||
if conn.debugChunks {
|
||||
data := fmt.Sprintf("rtmp: chunk id=%d msgsid=%d msgtypeid=%d msghdrtype=%d timestamp=%d ext=%v len=%d left=%d max=%d",
|
||||
csid, cs.msgsid, cs.msgtypeid, msghdrtype, cs.timenow, cs.hastimeext, cs.msgdatalen, cs.msgdataleft, conn.readMaxChunkSize)
|
||||
|
||||
if cs.msgtypeid != msgtypeidVideoMsg && cs.msgtypeid != msgtypeidAudioMsg {
|
||||
if len(cs.msgdata) > 1024 {
|
||||
data += " data=" + hex.EncodeToString(cs.msgdata[:1024]) + "... "
|
||||
} else {
|
||||
data += " data=" + hex.EncodeToString(cs.msgdata) + " "
|
||||
}
|
||||
} else {
|
||||
data += " data= "
|
||||
}
|
||||
|
||||
data += fmt.Sprintf("(%d bytes)", len(cs.msgdata))
|
||||
|
||||
fmt.Printf("%s\n", data)
|
||||
}
|
||||
|
||||
if cs.msgdataleft == 0 {
|
||||
@ -1521,20 +1614,27 @@ func (conn *Conn) readChunk() (err error) {
|
||||
timestamp = cs.timenow
|
||||
|
||||
if cs.msgtypeid == msgtypeidVideoMsg || cs.msgtypeid == msgtypeidAudioMsg {
|
||||
if !cs.gentimenow {
|
||||
if cs.prevtimenow >= cs.timenow {
|
||||
cs.tscount++
|
||||
} else {
|
||||
cs.tscount = 0
|
||||
if cs.msgcount < 20 { // only consider the first video and audio messages
|
||||
if !cs.gentimenow {
|
||||
if cs.prevtimenow >= cs.timenow {
|
||||
cs.tscount++
|
||||
} else {
|
||||
cs.tscount = 0
|
||||
}
|
||||
|
||||
// if the previous timestamp is the same as the current for too often in a row, assume defect timestamps
|
||||
if cs.tscount > 10 {
|
||||
cs.gentimenow = true
|
||||
}
|
||||
|
||||
cs.prevtimenow = cs.timenow
|
||||
}
|
||||
|
||||
if cs.tscount > 3 {
|
||||
cs.gentimenow = true
|
||||
}
|
||||
cs.msgcount++
|
||||
}
|
||||
|
||||
if cs.gentimenow {
|
||||
timestamp = uint32((time.Since(conn.start).Milliseconds() % 0xFFFFFFFF) & 0xFFFFFFFF)
|
||||
timestamp = uint32(time.Since(conn.start).Milliseconds() % 0xFFFFFFFF)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1542,19 +1642,11 @@ func (conn *Conn) readChunk() (err error) {
|
||||
return fmt.Errorf("handleMsg: %w", err)
|
||||
}
|
||||
|
||||
cs.prevtimenow = cs.timenow
|
||||
cs.msgdata = nil
|
||||
}
|
||||
|
||||
conn.ackn += uint32(n)
|
||||
|
||||
if conn.readAckSize != 0 && conn.ackn > conn.readAckSize {
|
||||
if err = conn.writeAck(conn.ackn, false); err != nil {
|
||||
return fmt.Errorf("writeACK: %w", err)
|
||||
}
|
||||
conn.flushWrite()
|
||||
conn.ackn = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -1608,6 +1700,7 @@ func (conn *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, ms
|
||||
switch msgtypeid {
|
||||
case msgtypeidCommandMsgAMF0:
|
||||
if _, err = conn.handleCommandMsgAMF0(msgdata); err != nil {
|
||||
err = fmt.Errorf("AMF0: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1618,6 +1711,7 @@ func (conn *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, ms
|
||||
}
|
||||
// skip first byte
|
||||
if _, err = conn.handleCommandMsgAMF0(msgdata[1:]); err != nil {
|
||||
err = fmt.Errorf("AMF3: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1670,8 +1764,14 @@ func (conn *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, ms
|
||||
|
||||
if metaindex != -1 && metaindex < len(conn.datamsgvals) {
|
||||
conn.metadata = conn.datamsgvals[metaindex].(flvio.AMFMap)
|
||||
//fmt.Printf("onMetadata: %+v\n", self.metadata)
|
||||
//fmt.Printf("videocodecid: %#08x (%f)\n", int64(self.metadata["videocodecid"].(float64)), self.metadata["videocodecid"].(float64))
|
||||
//fmt.Printf("onMetadata: %+v\n", conn.metadata)
|
||||
if _, hasVideo := conn.metadata["videocodecid"]; hasVideo {
|
||||
conn.prober.HasVideo = true
|
||||
}
|
||||
|
||||
if _, hasAudio := conn.metadata["audiocodecid"]; hasAudio {
|
||||
conn.prober.HasAudio = true
|
||||
}
|
||||
}
|
||||
|
||||
case msgtypeidVideoMsg:
|
||||
@ -1713,7 +1813,7 @@ func (conn *Conn) handleMsg(timestamp uint32, msgsid uint32, msgtypeid uint8, ms
|
||||
if len(conn.msgdata) != 4 {
|
||||
return fmt.Errorf("invalid packet of WindowAckSize")
|
||||
}
|
||||
conn.readAckSize = pio.U32BE(conn.msgdata)
|
||||
conn.readAckSize = pio.U32BE(conn.msgdata) >> 1
|
||||
default:
|
||||
if Debug {
|
||||
fmt.Printf("rtmp: unhandled msg: %d\n", msgtypeid)
|
||||
|
||||
13
vendor/github.com/fatih/color/color.go
generated
vendored
13
vendor/github.com/fatih/color/color.go
generated
vendored
@ -269,7 +269,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.wrap(fmt.Sprint(a...)))
|
||||
return fmt.Fprintln(w, c.wrap(sprintln(a...)))
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
@ -278,7 +278,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(Output, c.wrap(fmt.Sprint(a...)))
|
||||
return fmt.Fprintln(Output, c.wrap(sprintln(a...)))
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
@ -288,7 +288,7 @@ func (c *Color) Sprint(a ...interface{}) string {
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
return c.wrap(sprintln(a...)) + "\n"
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
@ -370,7 +370,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
return c.wrap(sprintln(a...)) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,3 +648,8 @@ func HiCyanString(format string, a ...interface{}) string { return colorString(f
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
||||
|
||||
// sprintln is a helper function to format a string with fmt.Sprintln and trim the trailing newline.
|
||||
func sprintln(a ...interface{}) string {
|
||||
return strings.TrimSuffix(fmt.Sprintln(a...), "\n")
|
||||
}
|
||||
|
||||
2
vendor/github.com/gabriel-vasile/mimetype/LICENSE
generated
vendored
2
vendor/github.com/gabriel-vasile/mimetype/LICENSE
generated
vendored
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2020 Gabriel Vasile
|
||||
Copyright (c) 2018 Gabriel Vasile
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
3
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
3
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
@ -16,9 +16,6 @@
|
||||
<a href="https://goreportcard.com/report/github.com/gabriel-vasile/mimetype">
|
||||
<img alt="Go report card" src="https://goreportcard.com/badge/github.com/gabriel-vasile/mimetype">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/gabriel-vasile/mimetype">
|
||||
<img alt="Code coverage" src="https://codecov.io/gh/gabriel-vasile/mimetype/branch/master/graph/badge.svg?token=qcfJF1kkl2"/>
|
||||
</a>
|
||||
<a href="LICENSE">
|
||||
<img alt="License" src="https://img.shields.io/badge/License-MIT-green.svg">
|
||||
</a>
|
||||
|
||||
119
vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go
generated
vendored
119
vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go
generated
vendored
@ -3,6 +3,7 @@ package magic
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -74,51 +75,87 @@ func CRX(raw []byte, limit uint32) bool {
|
||||
}
|
||||
|
||||
// Tar matches a (t)ape (ar)chive file.
|
||||
// Tar files are divided into 512 bytes records. First record contains a 257
|
||||
// bytes header padded with NUL.
|
||||
func Tar(raw []byte, _ uint32) bool {
|
||||
// The "magic" header field for files in in UStar (POSIX IEEE P1003.1) archives
|
||||
// has the prefix "ustar". The values of the remaining bytes in this field vary
|
||||
// by archiver implementation.
|
||||
if len(raw) >= 512 && bytes.HasPrefix(raw[257:], []byte{0x75, 0x73, 0x74, 0x61, 0x72}) {
|
||||
return true
|
||||
}
|
||||
const sizeRecord = 512
|
||||
|
||||
if len(raw) < 256 {
|
||||
// The structure of a tar header:
|
||||
// type TarHeader struct {
|
||||
// Name [100]byte
|
||||
// Mode [8]byte
|
||||
// Uid [8]byte
|
||||
// Gid [8]byte
|
||||
// Size [12]byte
|
||||
// Mtime [12]byte
|
||||
// Chksum [8]byte
|
||||
// Linkflag byte
|
||||
// Linkname [100]byte
|
||||
// Magic [8]byte
|
||||
// Uname [32]byte
|
||||
// Gname [32]byte
|
||||
// Devmajor [8]byte
|
||||
// Devminor [8]byte
|
||||
// }
|
||||
|
||||
if len(raw) < sizeRecord {
|
||||
return false
|
||||
}
|
||||
raw = raw[:sizeRecord]
|
||||
|
||||
// First 100 bytes of the header represent the file name.
|
||||
// Check if file looks like Gentoo GLEP binary package.
|
||||
if bytes.Contains(raw[:100], []byte("/gpkg-1\x00")) {
|
||||
return false
|
||||
}
|
||||
|
||||
// The older v7 format has no "magic" field, and therefore must be identified
|
||||
// with heuristics based on legal ranges of values for other header fields:
|
||||
// https://www.nationalarchives.gov.uk/PRONOM/Format/proFormatSearch.aspx?status=detailReport&id=385&strPageToDisplay=signatures
|
||||
rules := []struct {
|
||||
min, max uint8
|
||||
i int
|
||||
}{
|
||||
{0x21, 0xEF, 0},
|
||||
{0x30, 0x37, 105},
|
||||
{0x20, 0x37, 106},
|
||||
{0x00, 0x00, 107},
|
||||
{0x30, 0x37, 113},
|
||||
{0x20, 0x37, 114},
|
||||
{0x00, 0x00, 115},
|
||||
{0x30, 0x37, 121},
|
||||
{0x20, 0x37, 122},
|
||||
{0x00, 0x00, 123},
|
||||
{0x30, 0x37, 134},
|
||||
{0x30, 0x37, 146},
|
||||
{0x30, 0x37, 153},
|
||||
{0x00, 0x37, 154},
|
||||
// Get the checksum recorded into the file.
|
||||
recsum, err := tarParseOctal(raw[148:156])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, r := range rules {
|
||||
if raw[r.i] < r.min || raw[r.i] > r.max {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range []uint8{135, 147, 155} {
|
||||
if raw[i] != 0x00 && raw[i] != 0x20 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
sum1, sum2 := tarChksum(raw)
|
||||
return recsum == sum1 || recsum == sum2
|
||||
}
|
||||
|
||||
// tarParseOctal converts octal string to decimal int.
|
||||
func tarParseOctal(b []byte) (int64, error) {
|
||||
// Because unused fields are filled with NULs, we need to skip leading NULs.
|
||||
// Fields may also be padded with spaces or NULs.
|
||||
// So we remove leading and trailing NULs and spaces to be sure.
|
||||
b = bytes.Trim(b, " \x00")
|
||||
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
x, err := strconv.ParseUint(tarParseString(b), 8, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(x), nil
|
||||
}
|
||||
|
||||
// tarParseString converts a NUL ended bytes slice to a string.
|
||||
func tarParseString(b []byte) string {
|
||||
if i := bytes.IndexByte(b, 0); i >= 0 {
|
||||
return string(b[:i])
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// tarChksum computes the checksum for the header block b.
|
||||
// The actual checksum is written to same b block after it has been calculated.
|
||||
// Before calculation the bytes from b reserved for checksum have placeholder
|
||||
// value of ASCII space 0x20.
|
||||
// POSIX specifies a sum of the unsigned byte values, but the Sun tar used
|
||||
// signed byte values. We compute and return both.
|
||||
func tarChksum(b []byte) (unsigned, signed int64) {
|
||||
for i, c := range b {
|
||||
if 148 <= i && i < 156 {
|
||||
c = ' ' // Treat the checksum field itself as all spaces.
|
||||
}
|
||||
unsigned += int64(c)
|
||||
signed += int64(int8(c))
|
||||
}
|
||||
return unsigned, signed
|
||||
}
|
||||
|
||||
5
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
5
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
@ -153,8 +153,11 @@ func ftyp(sigs ...[]byte) Detector {
|
||||
if len(raw) < 12 {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(raw[4:8], []byte("ftyp")) {
|
||||
return false
|
||||
}
|
||||
for _, s := range sigs {
|
||||
if bytes.Equal(raw[4:12], append([]byte("ftyp"), s...)) {
|
||||
if bytes.Equal(raw[8:12], s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
44
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
generated
vendored
44
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
generated
vendored
@ -1,7 +1,6 @@
|
||||
package magic
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"time"
|
||||
@ -234,9 +233,10 @@ func GeoJSON(raw []byte, limit uint32) bool {
|
||||
// types.
|
||||
func NdJSON(raw []byte, limit uint32) bool {
|
||||
lCount, hasObjOrArr := 0, false
|
||||
sc := bufio.NewScanner(dropLastLine(raw, limit))
|
||||
for sc.Scan() {
|
||||
l := sc.Bytes()
|
||||
raw = dropLastLine(raw, limit)
|
||||
var l []byte
|
||||
for len(raw) != 0 {
|
||||
l, raw = scanLine(raw)
|
||||
// Empty lines are allowed in NDJSON.
|
||||
if l = trimRWS(trimLWS(l)); len(l) == 0 {
|
||||
continue
|
||||
@ -301,20 +301,15 @@ func Svg(raw []byte, limit uint32) bool {
|
||||
}
|
||||
|
||||
// Srt matches a SubRip file.
|
||||
func Srt(in []byte, _ uint32) bool {
|
||||
s := bufio.NewScanner(bytes.NewReader(in))
|
||||
if !s.Scan() {
|
||||
return false
|
||||
}
|
||||
// First line must be 1.
|
||||
if s.Text() != "1" {
|
||||
return false
|
||||
}
|
||||
func Srt(raw []byte, _ uint32) bool {
|
||||
line, raw := scanLine(raw)
|
||||
|
||||
if !s.Scan() {
|
||||
// First line must be 1.
|
||||
if string(line) != "1" {
|
||||
return false
|
||||
}
|
||||
secondLine := s.Text()
|
||||
line, raw = scanLine(raw)
|
||||
secondLine := string(line)
|
||||
// Timestamp format (e.g: 00:02:16,612 --> 00:02:19,376) limits secondLine
|
||||
// length to exactly 29 characters.
|
||||
if len(secondLine) != 29 {
|
||||
@ -325,14 +320,12 @@ func Srt(in []byte, _ uint32) bool {
|
||||
if strings.Contains(secondLine, ".") {
|
||||
return false
|
||||
}
|
||||
// For Go <1.17, comma is not recognised as a decimal separator by `time.Parse`.
|
||||
secondLine = strings.ReplaceAll(secondLine, ",", ".")
|
||||
// Second line must be a time range.
|
||||
ts := strings.Split(secondLine, " --> ")
|
||||
if len(ts) != 2 {
|
||||
return false
|
||||
}
|
||||
const layout = "15:04:05.000"
|
||||
const layout = "15:04:05,000"
|
||||
t0, err := time.Parse(layout, ts[0])
|
||||
if err != nil {
|
||||
return false
|
||||
@ -345,8 +338,9 @@ func Srt(in []byte, _ uint32) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
line, _ = scanLine(raw)
|
||||
// A third line must exist and not be empty. This is the actual subtitle text.
|
||||
return s.Scan() && len(s.Bytes()) != 0
|
||||
return len(line) != 0
|
||||
}
|
||||
|
||||
// Vtt matches a Web Video Text Tracks (WebVTT) file. See
|
||||
@ -373,3 +367,15 @@ func Vtt(raw []byte, limit uint32) bool {
|
||||
return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT"
|
||||
bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT"
|
||||
}
|
||||
|
||||
// dropCR drops a terminal \r from the data.
|
||||
func dropCR(data []byte) []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\r' {
|
||||
return data[0 : len(data)-1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
func scanLine(b []byte) (line, remainder []byte) {
|
||||
line, remainder, _ = bytes.Cut(b, []byte("\n"))
|
||||
return dropCR(line), remainder
|
||||
}
|
||||
|
||||
22
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
22
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
@ -18,7 +18,7 @@ func Tsv(raw []byte, limit uint32) bool {
|
||||
}
|
||||
|
||||
func sv(in []byte, comma rune, limit uint32) bool {
|
||||
r := csv.NewReader(dropLastLine(in, limit))
|
||||
r := csv.NewReader(bytes.NewReader(dropLastLine(in, limit)))
|
||||
r.Comma = comma
|
||||
r.ReuseRecord = true
|
||||
r.LazyQuotes = true
|
||||
@ -44,20 +44,14 @@ func sv(in []byte, comma rune, limit uint32) bool {
|
||||
// mimetype limits itself to ReadLimit bytes when performing a detection.
|
||||
// This means, for file formats like CSV for NDJSON, the last line of the input
|
||||
// can be an incomplete line.
|
||||
func dropLastLine(b []byte, cutAt uint32) io.Reader {
|
||||
if cutAt == 0 {
|
||||
return bytes.NewReader(b)
|
||||
func dropLastLine(b []byte, readLimit uint32) []byte {
|
||||
if readLimit == 0 || uint32(len(b)) < readLimit {
|
||||
return b
|
||||
}
|
||||
if uint32(len(b)) >= cutAt {
|
||||
for i := cutAt - 1; i > 0; i-- {
|
||||
if b[i] == '\n' {
|
||||
return bytes.NewReader(b[:i])
|
||||
}
|
||||
for i := len(b) - 1; i > 0; i-- {
|
||||
if b[i] == '\n' {
|
||||
return b[:i]
|
||||
}
|
||||
|
||||
// No newline was found between the 0 index and cutAt.
|
||||
return bytes.NewReader(b[:cutAt])
|
||||
}
|
||||
|
||||
return bytes.NewReader(b)
|
||||
return b
|
||||
}
|
||||
|
||||
8
vendor/github.com/gabriel-vasile/mimetype/mimetype.go
generated
vendored
8
vendor/github.com/gabriel-vasile/mimetype/mimetype.go
generated
vendored
@ -7,14 +7,15 @@ package mimetype
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var defaultLimit uint32 = 3072
|
||||
|
||||
// readLimit is the maximum number of bytes from the input used when detecting.
|
||||
var readLimit uint32 = 3072
|
||||
var readLimit uint32 = defaultLimit
|
||||
|
||||
// Detect returns the MIME type found from the provided byte slice.
|
||||
//
|
||||
@ -48,7 +49,7 @@ func DetectReader(r io.Reader) (*MIME, error) {
|
||||
// Using atomic because readLimit can be written at the same time in other goroutine.
|
||||
l := atomic.LoadUint32(&readLimit)
|
||||
if l == 0 {
|
||||
in, err = ioutil.ReadAll(r)
|
||||
in, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return errMIME, err
|
||||
}
|
||||
@ -103,6 +104,7 @@ func EqualsAny(s string, mimes ...string) bool {
|
||||
// SetLimit sets the maximum number of bytes read from input when detecting the MIME type.
|
||||
// Increasing the limit provides better detection for file formats which store
|
||||
// their magical numbers towards the end of the file: docx, pptx, xlsx, etc.
|
||||
// During detection data is read in a single block of size limit, i.e. it is not buffered.
|
||||
// A limit of 0 means the whole input file will be used.
|
||||
func SetLimit(limit uint32) {
|
||||
// Using atomic because readLimit can be read at the same time in other goroutine.
|
||||
|
||||
2
vendor/github.com/go-playground/validator/v10/README.md
generated
vendored
2
vendor/github.com/go-playground/validator/v10/README.md
generated
vendored
@ -1,7 +1,7 @@
|
||||
Package validator
|
||||
=================
|
||||
<img align="right" src="logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||

|
||||
[](https://travis-ci.org/go-playground/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
||||
|
||||
48
vendor/github.com/go-playground/validator/v10/baked_in.go
generated
vendored
48
vendor/github.com/go-playground/validator/v10/baked_in.go
generated
vendored
@ -64,8 +64,9 @@ var (
|
||||
// defines a common or complex set of validation(s) to simplify
|
||||
// adding validation to structs.
|
||||
bakedInAliases = map[string]string{
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
||||
"eu_country_code": "iso3166_1_alpha2_eu|iso3166_1_alpha3_eu|iso3166_1_alpha_numeric_eu",
|
||||
}
|
||||
|
||||
// bakedInValidators is the default map of ValidationFunc
|
||||
@ -133,6 +134,7 @@ var (
|
||||
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
||||
"file": isFile,
|
||||
"filepath": isFilePath,
|
||||
"base32": isBase32,
|
||||
"base64": isBase64,
|
||||
"base64url": isBase64URL,
|
||||
"base64rawurl": isBase64RawURL,
|
||||
@ -216,8 +218,11 @@ var (
|
||||
"datetime": isDatetime,
|
||||
"timezone": isTimeZone,
|
||||
"iso3166_1_alpha2": isIso3166Alpha2,
|
||||
"iso3166_1_alpha2_eu": isIso3166Alpha2EU,
|
||||
"iso3166_1_alpha3": isIso3166Alpha3,
|
||||
"iso3166_1_alpha3_eu": isIso3166Alpha3EU,
|
||||
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
|
||||
"iso3166_1_alpha_numeric_eu": isIso3166AlphaNumericEU,
|
||||
"iso3166_2": isIso31662,
|
||||
"iso4217": isIso4217,
|
||||
"iso4217_numeric": isIso4217Numeric,
|
||||
@ -1399,6 +1404,11 @@ func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool {
|
||||
return reg.MatchString(field.String())
|
||||
}
|
||||
|
||||
// isBase32 is the validation function for validating if the current field's value is a valid base 32.
|
||||
func isBase32(fl FieldLevel) bool {
|
||||
return base32Regex.MatchString(fl.Field().String())
|
||||
}
|
||||
|
||||
// isBase64 is the validation function for validating if the current field's value is a valid base 64.
|
||||
func isBase64(fl FieldLevel) bool {
|
||||
return base64Regex.MatchString(fl.Field().String())
|
||||
@ -2762,12 +2772,24 @@ func isIso3166Alpha2(fl FieldLevel) bool {
|
||||
return iso3166_1_alpha2[val]
|
||||
}
|
||||
|
||||
// isIso3166Alpha2EU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 European Union country code.
|
||||
func isIso3166Alpha2EU(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
return iso3166_1_alpha2_eu[val]
|
||||
}
|
||||
|
||||
// isIso3166Alpha3 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
|
||||
func isIso3166Alpha3(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
return iso3166_1_alpha3[val]
|
||||
}
|
||||
|
||||
// isIso3166Alpha3EU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 European Union country code.
|
||||
func isIso3166Alpha3EU(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
return iso3166_1_alpha3_eu[val]
|
||||
}
|
||||
|
||||
// isIso3166AlphaNumeric is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
|
||||
func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
@ -2790,6 +2812,28 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
||||
return iso3166_1_alpha_numeric[code]
|
||||
}
|
||||
|
||||
// isIso3166AlphaNumericEU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric European Union country code.
|
||||
func isIso3166AlphaNumericEU(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
var code int
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
i, err := strconv.Atoi(field.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
code = i % 1000
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
code = int(field.Int() % 1000)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
code = int(field.Uint() % 1000)
|
||||
default:
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
return iso3166_1_alpha_numeric_eu[code]
|
||||
}
|
||||
|
||||
// isIso31662 is the validation function for validating if the current field's value is a valid iso3166-2 code.
|
||||
func isIso31662(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
|
||||
27
vendor/github.com/go-playground/validator/v10/country_codes.go
generated
vendored
27
vendor/github.com/go-playground/validator/v10/country_codes.go
generated
vendored
@ -54,6 +54,15 @@ var iso3166_1_alpha2 = map[string]bool{
|
||||
"EH": true, "YE": true, "ZM": true, "ZW": true, "XK": true,
|
||||
}
|
||||
|
||||
var iso3166_1_alpha2_eu = map[string]bool{
|
||||
"AT": true, "BE": true, "BG": true, "HR": true, "CY": true,
|
||||
"CZ": true, "DK": true, "EE": true, "FI": true, "FR": true,
|
||||
"DE": true, "GR": true, "HU": true, "IE": true, "IT": true,
|
||||
"LV": true, "LT": true, "LU": true, "MT": true, "NL": true,
|
||||
"PL": true, "PT": true, "RO": true, "SK": true, "SI": true,
|
||||
"ES": true, "SE": true,
|
||||
}
|
||||
|
||||
var iso3166_1_alpha3 = map[string]bool{
|
||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||
"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
|
||||
@ -107,6 +116,15 @@ var iso3166_1_alpha3 = map[string]bool{
|
||||
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
|
||||
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true, "UNK": true,
|
||||
}
|
||||
|
||||
var iso3166_1_alpha3_eu = map[string]bool{
|
||||
"AUT": true, "BEL": true, "BGR": true, "HRV": true, "CYP": true,
|
||||
"CZE": true, "DNK": true, "EST": true, "FIN": true, "FRA": true,
|
||||
"DEU": true, "GRC": true, "HUN": true, "IRL": true, "ITA": true,
|
||||
"LVA": true, "LTU": true, "LUX": true, "MLT": true, "NLD": true,
|
||||
"POL": true, "PRT": true, "ROU": true, "SVK": true, "SVN": true,
|
||||
"ESP": true, "SWE": true,
|
||||
}
|
||||
var iso3166_1_alpha_numeric = map[int]bool{
|
||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||
4: true, 8: true, 12: true, 16: true, 20: true,
|
||||
@ -161,6 +179,15 @@ var iso3166_1_alpha_numeric = map[int]bool{
|
||||
887: true, 894: true, 716: true, 248: true, 153: true,
|
||||
}
|
||||
|
||||
var iso3166_1_alpha_numeric_eu = map[int]bool{
|
||||
40: true, 56: true, 100: true, 191: true, 196: true,
|
||||
200: true, 208: true, 233: true, 246: true, 250: true,
|
||||
276: true, 300: true, 348: true, 372: true, 380: true,
|
||||
428: true, 440: true, 442: true, 470: true, 528: true,
|
||||
616: true, 620: true, 642: true, 703: true, 705: true,
|
||||
724: true, 752: true,
|
||||
}
|
||||
|
||||
var iso3166_2 = map[string]bool{
|
||||
"AD-02": true, "AD-03": true, "AD-04": true, "AD-05": true, "AD-06": true,
|
||||
"AD-07": true, "AD-08": true, "AE-AJ": true, "AE-AZ": true, "AE-DU": true,
|
||||
|
||||
9
vendor/github.com/go-playground/validator/v10/doc.go
generated
vendored
9
vendor/github.com/go-playground/validator/v10/doc.go
generated
vendored
@ -916,6 +916,15 @@ according to the RFC 2141 spec.
|
||||
|
||||
Usage: urn_rfc2141
|
||||
|
||||
# Base32 String
|
||||
|
||||
This validates that a string value contains a valid bas324 value.
|
||||
Although an empty string is valid base32 this will report an empty string
|
||||
as an error, if you wish to accept an empty string as valid you can use
|
||||
this with the omitempty tag.
|
||||
|
||||
Usage: base32
|
||||
|
||||
# Base64 String
|
||||
|
||||
This validates that a string value contains a valid base64 value.
|
||||
|
||||
4
vendor/github.com/go-playground/validator/v10/regexes.go
generated
vendored
4
vendor/github.com/go-playground/validator/v10/regexes.go
generated
vendored
@ -17,6 +17,7 @@ const (
|
||||
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
|
||||
base32RegexString = "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=|[A-Z2-7]{8})$"
|
||||
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
||||
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
|
||||
base64RawURLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,4})$"
|
||||
@ -31,7 +32,7 @@ const (
|
||||
uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||
uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||
uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
||||
uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$"
|
||||
uLIDRegexString = "^(?i)[A-HJKMNP-TV-Z0-9]{26}$"
|
||||
md4RegexString = "^[0-9a-f]{32}$"
|
||||
md5RegexString = "^[0-9a-f]{32}$"
|
||||
sha256RegexString = "^[0-9a-f]{64}$"
|
||||
@ -89,6 +90,7 @@ var (
|
||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||
e164Regex = regexp.MustCompile(e164RegexString)
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
base32Regex = regexp.MustCompile(base32RegexString)
|
||||
base64Regex = regexp.MustCompile(base64RegexString)
|
||||
base64URLRegex = regexp.MustCompile(base64URLRegexString)
|
||||
base64RawURLRegex = regexp.MustCompile(base64RawURLRegexString)
|
||||
|
||||
3
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
3
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
@ -56,6 +56,9 @@ linters:
|
||||
- cyclop
|
||||
- containedctx
|
||||
- revive
|
||||
- nosnakecase
|
||||
- exhaustruct
|
||||
- depguard
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
|
||||
2
vendor/github.com/goccy/go-json/Makefile
generated
vendored
2
vendor/github.com/goccy/go-json/Makefile
generated
vendored
@ -30,7 +30,7 @@ golangci-lint: | $(BIN_DIR)
|
||||
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
|
||||
cd $$GOLANGCI_LINT_TMP_DIR; \
|
||||
go mod init tmp; \
|
||||
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0; \
|
||||
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2; \
|
||||
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
|
||||
}
|
||||
|
||||
|
||||
4
vendor/github.com/goccy/go-json/encode.go
generated
vendored
4
vendor/github.com/goccy/go-json/encode.go
generated
vendored
@ -52,7 +52,7 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...
|
||||
rctx.Option.Flag |= encoder.ContextOption
|
||||
rctx.Option.Context = ctx
|
||||
|
||||
err := e.encodeWithOption(rctx, v, optFuncs...)
|
||||
err := e.encodeWithOption(rctx, v, optFuncs...) //nolint: contextcheck
|
||||
|
||||
encoder.ReleaseRuntimeContext(rctx)
|
||||
return err
|
||||
@ -120,7 +120,7 @@ func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOption
|
||||
optFunc(rctx.Option)
|
||||
}
|
||||
|
||||
buf, err := encode(rctx, v)
|
||||
buf, err := encode(rctx, v) //nolint: contextcheck
|
||||
if err != nil {
|
||||
encoder.ReleaseRuntimeContext(rctx)
|
||||
return nil, err
|
||||
|
||||
1
vendor/github.com/goccy/go-json/internal/decoder/ptr.go
generated
vendored
1
vendor/github.com/goccy/go-json/internal/decoder/ptr.go
generated
vendored
@ -85,6 +85,7 @@ func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
|
||||
}
|
||||
c, err := d.dec.Decode(ctx, cursor, depth, newptr)
|
||||
if err != nil {
|
||||
*(*unsafe.Pointer)(p) = nil
|
||||
return 0, err
|
||||
}
|
||||
cursor = c
|
||||
|
||||
2
vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go
generated
vendored
2
vendor/github.com/goccy/go-json/internal/decoder/unmarshal_text.go
generated
vendored
@ -147,7 +147,7 @@ func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int
|
||||
return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
|
||||
}
|
||||
|
||||
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||
func unquoteBytes(s []byte) (t []byte, ok bool) { //nolint: nonamedreturns
|
||||
length := len(s)
|
||||
if length < 2 || s[0] != '"' || s[length-1] != '"' {
|
||||
return
|
||||
|
||||
2
vendor/github.com/goccy/go-json/internal/encoder/compact.go
generated
vendored
2
vendor/github.com/goccy/go-json/internal/encoder/compact.go
generated
vendored
@ -213,8 +213,8 @@ func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, e
|
||||
dst = append(dst, src[start:cursor]...)
|
||||
dst = append(dst, `\u202`...)
|
||||
dst = append(dst, hex[src[cursor+2]&0xF])
|
||||
cursor += 2
|
||||
start = cursor + 3
|
||||
cursor += 2
|
||||
}
|
||||
}
|
||||
switch c {
|
||||
|
||||
2
vendor/github.com/goccy/go-json/internal/encoder/compiler.go
generated
vendored
2
vendor/github.com/goccy/go-json/internal/encoder/compiler.go
generated
vendored
@ -480,7 +480,7 @@ func (c *Compiler) mapCode(typ *runtime.Type) (*MapCode, error) {
|
||||
|
||||
func (c *Compiler) listElemCode(typ *runtime.Type) (Code, error) {
|
||||
switch {
|
||||
case c.isPtrMarshalJSONType(typ):
|
||||
case c.implementsMarshalJSONType(typ) || c.implementsMarshalJSONType(runtime.PtrTo(typ)):
|
||||
return c.marshalJSONCode(typ)
|
||||
case !typ.Implements(marshalTextType) && runtime.PtrTo(typ).Implements(marshalTextType):
|
||||
return c.marshalTextCode(typ)
|
||||
|
||||
24
vendor/github.com/goccy/go-json/internal/encoder/int.go
generated
vendored
24
vendor/github.com/goccy/go-json/internal/encoder/int.go
generated
vendored
@ -1,3 +1,27 @@
|
||||
// This files's processing codes are inspired by https://github.com/segmentio/encoding.
|
||||
// The license notation is as follows.
|
||||
//
|
||||
// # MIT License
|
||||
//
|
||||
// Copyright (c) 2019 Segment.io, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
package encoder
|
||||
|
||||
import (
|
||||
|
||||
24
vendor/github.com/goccy/go-json/internal/encoder/string.go
generated
vendored
24
vendor/github.com/goccy/go-json/internal/encoder/string.go
generated
vendored
@ -1,3 +1,27 @@
|
||||
// This files's string processing codes are inspired by https://github.com/segmentio/encoding.
|
||||
// The license notation is as follows.
|
||||
//
|
||||
// # MIT License
|
||||
//
|
||||
// Copyright (c) 2019 Segment.io, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
package encoder
|
||||
|
||||
import (
|
||||
|
||||
1
vendor/github.com/goccy/go-json/internal/runtime/rtype.go
generated
vendored
1
vendor/github.com/goccy/go-json/internal/runtime/rtype.go
generated
vendored
@ -252,7 +252,6 @@ func IfaceIndir(*Type) bool
|
||||
//go:noescape
|
||||
func RType2Type(t *Type) reflect.Type
|
||||
|
||||
//go:nolint structcheck
|
||||
type emptyInterface struct {
|
||||
_ *Type
|
||||
ptr unsafe.Pointer
|
||||
|
||||
35
vendor/github.com/goccy/go-json/json.go
generated
vendored
35
vendor/github.com/goccy/go-json/json.go
generated
vendored
@ -89,31 +89,31 @@ type UnmarshalerContext interface {
|
||||
//
|
||||
// Examples of struct field tags and their meanings:
|
||||
//
|
||||
// // Field appears in JSON as key "myName".
|
||||
// Field int `json:"myName"`
|
||||
// // Field appears in JSON as key "myName".
|
||||
// Field int `json:"myName"`
|
||||
//
|
||||
// // Field appears in JSON as key "myName" and
|
||||
// // the field is omitted from the object if its value is empty,
|
||||
// // as defined above.
|
||||
// Field int `json:"myName,omitempty"`
|
||||
// // Field appears in JSON as key "myName" and
|
||||
// // the field is omitted from the object if its value is empty,
|
||||
// // as defined above.
|
||||
// Field int `json:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in JSON as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// // Note the leading comma.
|
||||
// Field int `json:",omitempty"`
|
||||
// // Field appears in JSON as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// // Note the leading comma.
|
||||
// Field int `json:",omitempty"`
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `json:"-"`
|
||||
// // Field is ignored by this package.
|
||||
// Field int `json:"-"`
|
||||
//
|
||||
// // Field appears in JSON as key "-".
|
||||
// Field int `json:"-,"`
|
||||
// // Field appears in JSON as key "-".
|
||||
// Field int `json:"-,"`
|
||||
//
|
||||
// The "string" option signals that a field is stored as JSON inside a
|
||||
// JSON-encoded string. It applies only to fields of string, floating point,
|
||||
// integer, or boolean types. This extra level of encoding is sometimes used
|
||||
// when communicating with JavaScript programs:
|
||||
//
|
||||
// Int64String int64 `json:",string"`
|
||||
// Int64String int64 `json:",string"`
|
||||
//
|
||||
// The key name will be used if it's a non-empty string consisting of
|
||||
// only Unicode letters, digits, and ASCII punctuation except quotation
|
||||
@ -166,7 +166,6 @@ type UnmarshalerContext interface {
|
||||
// JSON cannot represent cyclic data structures and Marshal does not
|
||||
// handle them. Passing cyclic structures to Marshal will result in
|
||||
// an infinite recursion.
|
||||
//
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
return MarshalWithOption(v)
|
||||
}
|
||||
@ -264,14 +263,13 @@ func MarshalIndentWithOption(v interface{}, prefix, indent string, optFuncs ...E
|
||||
//
|
||||
// The JSON null value unmarshals into an interface, map, pointer, or slice
|
||||
// by setting that Go value to nil. Because null is often used in JSON to mean
|
||||
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
|
||||
// “not present,” unmarshaling a JSON null into any other Go type has no effect
|
||||
// on the value and produces no error.
|
||||
//
|
||||
// When unmarshaling quoted strings, invalid UTF-8 or
|
||||
// invalid UTF-16 surrogate pairs are not treated as an error.
|
||||
// Instead, they are replaced by the Unicode replacement
|
||||
// character U+FFFD.
|
||||
//
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
return unmarshal(data, v)
|
||||
}
|
||||
@ -299,7 +297,6 @@ func UnmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc)
|
||||
// Number, for JSON numbers
|
||||
// string, for JSON string literals
|
||||
// nil, for JSON null
|
||||
//
|
||||
type Token = json.Token
|
||||
|
||||
// A Number represents a JSON number literal.
|
||||
|
||||
205
vendor/github.com/mholt/acmez/acme/ari.go
generated
vendored
205
vendor/github.com/mholt/acmez/acme/ari.go
generated
vendored
@ -1,205 +0,0 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// RenewalInfo "is a new resource type introduced to ACME protocol.
|
||||
// This new resource both allows clients to query the server for
|
||||
// suggestions on when they should renew certificates, and allows
|
||||
// clients to inform the server when they have completed renewal
|
||||
// (or otherwise replaced the certificate to their satisfaction)."
|
||||
//
|
||||
// ACME Renewal Information (ARI):
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
|
||||
//
|
||||
// This is a DRAFT specification and the API is subject to change.
|
||||
type RenewalInfo struct {
|
||||
SuggestedWindow struct {
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
} `json:"suggestedWindow"`
|
||||
ExplanationURL string `json:"explanationURL"`
|
||||
|
||||
// This field is not part of the specified structure, but is
|
||||
// important for proper conformance to the specification,
|
||||
// so the Retry-After response header will be read and this
|
||||
// field will be populated for ACME client consideration.
|
||||
// Polling again for renewal info should not occur before
|
||||
// this time.
|
||||
RetryAfter time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// GetRenewalInfo returns the ACME Renewal Information (ARI) for the certificate represented by the
|
||||
// "base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960]" without padding
|
||||
// (call `CertIDSequence()` to get this value). It tacks on the Retry-After value if present.
|
||||
func (c *Client) GetRenewalInfo(ctx context.Context, b64CertIDSeq string) (RenewalInfo, error) {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return RenewalInfo{}, err
|
||||
}
|
||||
|
||||
endpoint := c.dir.RenewalInfo + b64CertIDSeq
|
||||
|
||||
var ari RenewalInfo
|
||||
resp, err := c.httpReq(ctx, http.MethodGet, endpoint, nil, &ari)
|
||||
if err != nil {
|
||||
return RenewalInfo{}, err
|
||||
}
|
||||
|
||||
ra, err := retryAfterTime(resp)
|
||||
if err != nil && c.Logger != nil {
|
||||
c.Logger.Error("setting Retry-After value", zap.Error(err))
|
||||
}
|
||||
ari.RetryAfter = ra
|
||||
|
||||
return ari, nil
|
||||
}
|
||||
|
||||
// UpdateRenewalInfo notifies the ACME server that the certificate represented by b64CertIDSeq
|
||||
// has been replaced. The b64CertIDSeq string can be obtained by calling `CertIDSequence()`.
|
||||
func (c *Client) UpdateRenewalInfo(ctx context.Context, account Account, b64CertIDSeq string) error {
|
||||
if err := c.provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := struct {
|
||||
CertID string `json:"certID"`
|
||||
Replaced bool `json:"replaced"`
|
||||
}{
|
||||
CertID: b64CertIDSeq,
|
||||
Replaced: true,
|
||||
}
|
||||
|
||||
resp, err := c.httpPostJWS(ctx, account.PrivateKey, account.Location, c.dir.RenewalInfo, payload, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("updating renewal status: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertIDSequence returns the "base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960]"
|
||||
// without padding for the given certificate chain. It is used primarily for requests to OCSP and ARI.
|
||||
//
|
||||
// The certificate chain must contain at least two elements: an end-entity certificate first, followed by an issuer
|
||||
// certificate second. Of the end-entity certificate, only the SerialNumber field is required; and of the issuer
|
||||
// certificate, only the RawSubjectPublicKeyInfo and RawSubject fields are required. If the issuer certificate is
|
||||
// not provided, then it will be downloaded if the end-entity certificate contains the IssuingCertificateURL.
|
||||
//
|
||||
// As the return value may be used often during a certificate's lifetime, and in bulk with potentially tens of
|
||||
// thousands of other certificates, it may be preferable to store or cache this value so that ASN.1 documents do
|
||||
// not need to be repeatedly decoded and re-encoded.
|
||||
func CertIDSequence(_ context.Context, certChain []*x509.Certificate, hash crypto.Hash, client *http.Client) (string, error) {
|
||||
endEntityCert := certChain[0]
|
||||
|
||||
// if no chain was provided, we'll need to download the issuer cert
|
||||
if len(certChain) == 1 {
|
||||
if len(endEntityCert.IssuingCertificateURL) == 0 {
|
||||
return "", fmt.Errorf("no URL to issuing certificate")
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
resp, err := client.Get(endEntityCert.IssuingCertificateURL[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting issuer certificate: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading issuer certificate: %v", err)
|
||||
}
|
||||
|
||||
issuerCert, err := x509.ParseCertificate(issuerBytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing issuer certificate: %v", err)
|
||||
}
|
||||
|
||||
certChain = append(certChain, issuerCert)
|
||||
}
|
||||
|
||||
issuerCert := certChain[1]
|
||||
|
||||
hashAlg, ok := hashOIDs[hash]
|
||||
if !ok {
|
||||
return "", x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
if !hash.Available() {
|
||||
return "", x509.ErrUnsupportedAlgorithm
|
||||
}
|
||||
h := hash.New()
|
||||
|
||||
var publicKeyInfo struct {
|
||||
Algorithm pkix.AlgorithmIdentifier
|
||||
PublicKey asn1.BitString
|
||||
}
|
||||
if _, err := asn1.Unmarshal(issuerCert.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h.Write(publicKeyInfo.PublicKey.RightAlign())
|
||||
issuerKeyHash := h.Sum(nil)
|
||||
|
||||
h.Reset()
|
||||
h.Write(issuerCert.RawSubject)
|
||||
issuerNameHash := h.Sum(nil)
|
||||
|
||||
val, err := asn1.Marshal(certID{
|
||||
HashAlgorithm: pkix.AlgorithmIdentifier{
|
||||
Algorithm: hashAlg,
|
||||
},
|
||||
NameHash: issuerNameHash,
|
||||
IssuerKeyHash: issuerKeyHash,
|
||||
SerialNumber: endEntityCert.SerialNumber,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(val), nil
|
||||
}
|
||||
|
||||
type certID struct {
|
||||
HashAlgorithm pkix.AlgorithmIdentifier
|
||||
NameHash []byte
|
||||
IssuerKeyHash []byte
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
|
||||
crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
|
||||
crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
|
||||
crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
|
||||
crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
|
||||
}
|
||||
149
vendor/github.com/mholt/acmez/csr.go
generated
vendored
149
vendor/github.com/mholt/acmez/csr.go
generated
vendored
@ -1,149 +0,0 @@
|
||||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acmez
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
|
||||
"github.com/mholt/acmez/acme"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
|
||||
)
|
||||
|
||||
var (
|
||||
oidExtensionSubjectAltName = []int{2, 5, 29, 17}
|
||||
oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3}
|
||||
oidHardwareModuleName = []int{1, 3, 6, 1, 5, 5, 7, 8, 4}
|
||||
)
|
||||
|
||||
// RFC 5280 - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
|
||||
//
|
||||
// OtherName ::= SEQUENCE {
|
||||
// type-id OBJECT IDENTIFIER,
|
||||
// value [0] EXPLICIT ANY DEFINED BY type-id }
|
||||
type otherName struct {
|
||||
TypeID asn1.ObjectIdentifier
|
||||
Value asn1.RawValue
|
||||
}
|
||||
|
||||
// permanentIdentifier is defined in RFC 4043 as an optional feature that can be
|
||||
// used by a CA to indicate that two or more certificates relate to the same
|
||||
// entity.
|
||||
//
|
||||
// The OID defined for this SAN is "1.3.6.1.5.5.7.8.3".
|
||||
//
|
||||
// See https://www.rfc-editor.org/rfc/rfc4043
|
||||
//
|
||||
// PermanentIdentifier ::= SEQUENCE {
|
||||
// identifierValue UTF8String OPTIONAL,
|
||||
// assigner OBJECT IDENTIFIER OPTIONAL
|
||||
// }
|
||||
type permanentIdentifier struct {
|
||||
IdentifierValue string `asn1:"utf8,optional"`
|
||||
Assigner asn1.ObjectIdentifier `asn1:"optional"`
|
||||
}
|
||||
|
||||
// hardwareModuleName is defined in RFC 4108 as an optional feature that can be
|
||||
// used to identify a hardware module.
|
||||
//
|
||||
// The OID defined for this SAN is "1.3.6.1.5.5.7.8.4".
|
||||
//
|
||||
// See https://www.rfc-editor.org/rfc/rfc4108#section-5
|
||||
//
|
||||
// HardwareModuleName ::= SEQUENCE {
|
||||
// hwType OBJECT IDENTIFIER,
|
||||
// hwSerialNum OCTET STRING
|
||||
// }
|
||||
type hardwareModuleName struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
SerialNumber []byte `asn1:"tag:4"`
|
||||
}
|
||||
|
||||
func forEachSAN(der cryptobyte.String, callback func(tag int, data []byte) error) error {
|
||||
if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) {
|
||||
return errors.New("invalid subject alternative name extension")
|
||||
}
|
||||
for !der.Empty() {
|
||||
var san cryptobyte.String
|
||||
var tag cryptobyte_asn1.Tag
|
||||
if !der.ReadAnyASN1Element(&san, &tag) {
|
||||
return errors.New("invalid subject alternative name extension")
|
||||
}
|
||||
if err := callback(int(tag^0x80), san); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createIdentifiersUsingCSR extracts the list of ACME identifiers from the
|
||||
// given Certificate Signing Request.
|
||||
func createIdentifiersUsingCSR(csr *x509.CertificateRequest) ([]acme.Identifier, error) {
|
||||
var ids []acme.Identifier
|
||||
for _, name := range csr.DNSNames {
|
||||
ids = append(ids, acme.Identifier{
|
||||
Type: "dns", // RFC 8555 §9.7.7
|
||||
Value: name,
|
||||
})
|
||||
}
|
||||
for _, ip := range csr.IPAddresses {
|
||||
ids = append(ids, acme.Identifier{
|
||||
Type: "ip", // RFC 8738
|
||||
Value: ip.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// Extract permanent identifiers and hardware module values.
|
||||
// This block will ignore errors.
|
||||
for _, ext := range csr.Extensions {
|
||||
if ext.Id.Equal(oidExtensionSubjectAltName) {
|
||||
err := forEachSAN(ext.Value, func(tag int, data []byte) error {
|
||||
var on otherName
|
||||
if rest, err := asn1.UnmarshalWithParams(data, &on, "tag:0"); err != nil || len(rest) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case on.TypeID.Equal(oidPermanentIdentifier):
|
||||
var pi permanentIdentifier
|
||||
if _, err := asn1.Unmarshal(on.Value.Bytes, &pi); err == nil {
|
||||
ids = append(ids, acme.Identifier{
|
||||
Type: "permanent-identifier", // draft-acme-device-attest-00 §3
|
||||
Value: pi.IdentifierValue,
|
||||
})
|
||||
}
|
||||
case on.TypeID.Equal(oidHardwareModuleName):
|
||||
var hmn hardwareModuleName
|
||||
if _, err := asn1.Unmarshal(on.Value.Bytes, &hmn); err == nil {
|
||||
ids = append(ids, acme.Identifier{
|
||||
Type: "hardware-module", // draft-acme-device-attest-00 §4
|
||||
Value: string(hmn.SerialNumber),
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
0
vendor/github.com/mholt/acmez/LICENSE → vendor/github.com/mholt/acmez/v2/LICENSE
generated
vendored
0
vendor/github.com/mholt/acmez/LICENSE → vendor/github.com/mholt/acmez/v2/LICENSE
generated
vendored
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user