zero: merge pomerium/zero-sdk (#4848)

This commit is contained in:
Denis Mishin 2023-12-11 17:31:39 -05:00 committed by GitHub
parent c4dd965f2d
commit ea64902a73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 4800 additions and 170 deletions

49
go.mod
View file

@ -43,6 +43,7 @@ require (
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.5.0
github.com/natefinch/atomic v1.0.1
github.com/oapi-codegen/runtime v1.1.0
github.com/open-policy-agent/opa v0.59.0
github.com/openzipkin/zipkin-go v0.4.2
github.com/ory/dockertest/v3 v3.10.0
@ -50,7 +51,6 @@ require (
github.com/pomerium/csrf v1.7.0
github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524
github.com/pomerium/webauthn v0.0.0-20221118023040-00a9c430578b
github.com/pomerium/zero-sdk v0.0.0-20231127153820-dcd408d87b54
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.45.0
@ -67,7 +67,7 @@ require (
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.16.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/net v0.19.0
golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0
@ -75,7 +75,7 @@ require (
google.golang.org/api v0.153.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
google.golang.org/protobuf v1.31.1-0.20231027082548-f4a6c1f6e5c1
gopkg.in/yaml.v3 v3.0.1
namespacelabs.dev/go-filenotify v0.0.0-20220511192020-53ea11be7eaa
sigs.k8s.io/yaml v1.4.0
@ -110,45 +110,31 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
github.com/aws/smithy-go v1.16.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bufbuild/buf v1.26.1 // indirect
github.com/bufbuild/connect-go v1.9.0 // indirect
github.com/bufbuild/connect-opentelemetry-go v0.4.0 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
github.com/containerd/continuity v0.4.2 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.15.0 // indirect
github.com/docker/cli v24.0.4+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/getkin/kin-openapi v0.118.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-containerregistry v0.15.2 // indirect
github.com/google/go-tpm v0.3.3 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
@ -157,65 +143,49 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
github.com/perimeterx/marshmallow v1.1.4 // indirect
github.com/philhofer/fwd v1.0.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/statsd_exporter v0.22.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/tetratelabs/wazero v1.3.1 // indirect
github.com/tinylib/msgp v1.1.2 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
@ -227,19 +197,16 @@ require (
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/sdk v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/tools v0.15.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.22.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect

134
go.sum
View file

@ -55,7 +55,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
@ -140,14 +139,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bits-and-blooms/bitset v1.11.0 h1:RMyy2mBBShArUAhfVRZJ2xyBO58KCBCtZFShw3umo6k=
github.com/bits-and-blooms/bitset v1.11.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bufbuild/buf v1.26.1 h1:+GdU4z2paCmDclnjLv7MqnVi3AGviImlIKhG0MHH9FA=
github.com/bufbuild/buf v1.26.1/go.mod h1:UMPncXMWgrmIM+0QpwTEwjNr2SA0z2YIVZZsmNflvB4=
github.com/bufbuild/connect-go v1.9.0 h1:JIgAeNuFpo+SUPfU19Yt5TcWlznsN5Bv10/gI/6Pjoc=
github.com/bufbuild/connect-go v1.9.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
github.com/bufbuild/connect-opentelemetry-go v0.4.0 h1:6JAn10SNqlQ/URhvRNGrIlczKw1wEXknBUUtmWqOiak=
github.com/bufbuild/connect-opentelemetry-go v0.4.0/go.mod h1:nwPXYoDOoc2DGyKE/6pT1Q9MPSi2Et2e6BieMD0l6WU=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
@ -179,8 +170,6 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWH
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@ -194,18 +183,12 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
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=
github.com/deepmap/oapi-codegen v1.15.0 h1:SQqViaeb4k2vMul8gx12oDOIadEtoRqTdLkxjzqtQ90=
github.com/deepmap/oapi-codegen v1.15.0/go.mod h1:a6KoHV7lMRwsPoEg2C6NDHiXYV3EQfiFocOlJ8dgJQE=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
@ -214,14 +197,14 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw=
github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@ -243,8 +226,6 @@ github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQ
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -261,8 +242,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@ -291,25 +270,14 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -379,8 +347,6 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
@ -406,9 +372,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
@ -426,7 +389,6 @@ github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qK
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@ -456,14 +418,9 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@ -472,15 +429,9 @@ github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -502,7 +453,6 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -526,11 +476,6 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/martinlindhe/base36 v1.1.1 h1:1F1MZ5MGghBXDZ2KJ3QfxmiydlWOGB8HCEtkap5NkVg=
github.com/martinlindhe/base36 v1.1.1/go.mod h1:vMS8PaZ5e/jV9LwFKlm0YLnXl/hpOihiBxKkIoc3g08=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -552,7 +497,6 @@ github.com/minio/minio-go/v7 v7.0.65 h1:sOlB8T3nQK+TApTpuN3k4WD5KasvZIE3vVFzyyCa
github.com/minio/minio-go/v7 v7.0.65/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
@ -569,8 +513,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@ -578,12 +520,11 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM=
github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -613,8 +554,6 @@ github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnz
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
@ -623,15 +562,11 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1 h1:VGcrWe3yk6o+t7BdV
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -641,8 +576,6 @@ github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524 h1:3YQY1sb5
github.com/pomerium/datasource v0.18.2-0.20221108160055-c6134b5ed524/go.mod h1:7fGbUYJnU8RcxZJvUvhukOIBv1G7LWDAHMfDxAf5+Y0=
github.com/pomerium/webauthn v0.0.0-20221118023040-00a9c430578b h1:oll/aOfJudnqFAwCvoXK9+WN2zVjTzHVPLXCggHQmHk=
github.com/pomerium/webauthn v0.0.0-20221118023040-00a9c430578b/go.mod h1:KswTenBBh4y1pmhU2dpm8VgJQCgSErCg7OOFTeebrNc=
github.com/pomerium/zero-sdk v0.0.0-20231127153820-dcd408d87b54 h1:pIjWOs15zJq9XhU1uHKhBjqc6X7cJHRYbx+OW4v3EiA=
github.com/pomerium/zero-sdk v0.0.0-20231127153820-dcd408d87b54/go.mod h1:b3979nx45mwDaeIQQe5T11XjxMUQntc4H3v+szdsHUc=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
@ -690,8 +623,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
@ -700,8 +633,6 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@ -716,7 +647,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -729,8 +659,6 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -765,8 +693,6 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/tetratelabs/wazero v1.3.1 h1:rnb9FgOEQRLLR8tgoD1mfjNjMhFeWRUk+a4b4j/GpUM=
github.com/tetratelabs/wazero v1.3.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
@ -779,19 +705,11 @@ github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f/go.mod h1:N+sR0vL
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/volatiletech/null/v9 v9.0.0 h1:JCdlHEiSRVxOi7/MABiEfdsqmuj9oTV20Ao7VvZ0JkE=
@ -847,15 +765,11 @@ go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ3
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
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=
@ -889,8 +803,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -904,8 +818,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -918,8 +830,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1051,11 +963,9 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1066,7 +976,6 @@ golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
@ -1077,8 +986,6 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1148,8 +1055,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1248,8 +1155,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1265,16 +1170,14 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.1-0.20231027082548-f4a6c1f6e5c1 h1:fk72uXZyuZiTtW5tgd63jyVK6582lF61nRC/kGv6vCA=
google.golang.org/protobuf v1.31.1-0.20231027082548-f4a6c1f6e5c1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/DataDog/dd-trace-go.v1 v1.22.0 h1:gpWsqqkwUldNZXGJqT69NU9MdEDhLboK1C4nMgR0MWw=
gopkg.in/DataDog/dd-trace-go.v1 v1.22.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg=
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -1296,7 +1199,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -20,8 +20,8 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/retry"
sdk "github.com/pomerium/zero-sdk"
connect_mux "github.com/pomerium/zero-sdk/connect-mux"
sdk "github.com/pomerium/pomerium/pkg/zero"
connect_mux "github.com/pomerium/pomerium/pkg/zero/connect-mux"
)
const (

View file

@ -15,7 +15,7 @@ import (
"os"
"github.com/pomerium/pomerium/pkg/cryptutil"
cluster_api "github.com/pomerium/zero-sdk/cluster"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
)
// LoadBootstrapConfigFromFile loads the bootstrap configuration from a file.

View file

@ -8,7 +8,7 @@ import (
"github.com/pomerium/pomerium/internal/zero/bootstrap"
"github.com/pomerium/pomerium/pkg/cryptutil"
cluster_api "github.com/pomerium/zero-sdk/cluster"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
)
func TestFile(t *testing.T) {

View file

@ -16,7 +16,7 @@ import (
"github.com/pomerium/pomerium/internal/deterministicecdsa"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/netutil"
sdk "github.com/pomerium/zero-sdk"
sdk "github.com/pomerium/pomerium/pkg/zero"
)
// Source is a base config layer for Pomerium

View file

@ -9,7 +9,7 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
cluster_api "github.com/pomerium/zero-sdk/cluster"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
)
var (

View file

@ -16,7 +16,7 @@ import (
"github.com/pomerium/pomerium/internal/zero/reconciler"
"github.com/pomerium/pomerium/pkg/cmd/pomerium"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
sdk "github.com/pomerium/zero-sdk"
sdk "github.com/pomerium/pomerium/pkg/zero"
)
// Run runs Pomerium is managed mode using the provided token.

View file

@ -6,7 +6,7 @@ import (
"github.com/rs/zerolog"
"github.com/pomerium/pomerium/internal/log"
connect_mux "github.com/pomerium/zero-sdk/connect-mux"
connect_mux "github.com/pomerium/pomerium/pkg/zero/connect-mux"
)
func (c *controller) RunConnectLog(ctx context.Context) error {

View file

@ -7,7 +7,7 @@ import (
"time"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
sdk "github.com/pomerium/zero-sdk"
sdk "github.com/pomerium/pomerium/pkg/zero"
)
// reconcilerConfig contains the configuration for the resource bundles reconciler.

View file

@ -15,7 +15,7 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/protoutil"
zero_sdk "github.com/pomerium/zero-sdk"
zero_sdk "github.com/pomerium/pomerium/pkg/zero"
)
// BundleCacheEntry is a cache entry for a bundle

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/internal/zero/reconciler"
zero_sdk "github.com/pomerium/zero-sdk"
zero_sdk "github.com/pomerium/pomerium/pkg/zero"
)
func TestCacheEntryProto(t *testing.T) {

View file

@ -4,7 +4,7 @@ import (
"context"
"github.com/pomerium/pomerium/internal/log"
cluster_api "github.com/pomerium/zero-sdk/cluster"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
)
const (

View file

@ -15,7 +15,7 @@ import (
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
connect_mux "github.com/pomerium/zero-sdk/connect-mux"
connect_mux "github.com/pomerium/pomerium/pkg/zero/connect-mux"
)
type service struct {

116
pkg/fanout/config.go Normal file
View file

@ -0,0 +1,116 @@
package fanout
import "time"
const (
defaultPublishTimeout = time.Second
minPublishTimeout = time.Millisecond * 100
defaultReceiverCallbackTimeout = time.Second
minReceiverCallbackTimeout = time.Millisecond * 100
defaultAddSubscriberTimeout = time.Millisecond * 100
minAddSubscriberTimeout = time.Millisecond * 100
defaultReceiverBufferSize = 100
defaultMessageBufferSize = 1024
defaultSubscriberBufferSize = 100
)
type config struct {
publishTimeout time.Duration
receiverBufferSize int
receiverCallbackTimeout time.Duration
publishBufferSize int
subscriberBufferSize int
addSubscriberTimeout time.Duration
}
// Option configures a FanOut
type Option func(*config)
// WithPublishTimeout sets the internal timeout for publishing messages to the fanout
func WithPublishTimeout(timeout time.Duration) Option {
if timeout < defaultPublishTimeout {
timeout = defaultPublishTimeout
}
return func(c *config) {
c.publishTimeout = timeout
}
}
// WithReceiverBufferSize sets the buffer size for the buffer between fanout and subscriber receiver
func WithReceiverBufferSize(size int) Option {
if size < 1 {
size = 1
}
return func(c *config) {
c.receiverBufferSize = size
}
}
// WithReceiverCallbackTimeout sets the timeout for the callback function of the receiver
func WithReceiverCallbackTimeout(timeout time.Duration) Option {
if timeout < minReceiverCallbackTimeout {
timeout = minReceiverCallbackTimeout
}
return func(c *config) {
c.receiverCallbackTimeout = timeout
}
}
// WithMessagesBufferSize sets the buffer size for the buffer that holds messages to be published
func WithMessagesBufferSize(size int) Option {
if size < 1 {
size = 1
}
return func(c *config) {
c.publishBufferSize = size
}
}
// WithSubscriberBufferSize sets the new subscriber requsts buffer size
func WithSubscriberBufferSize(size int) Option {
if size < 1 {
size = 1
}
return func(c *config) {
c.subscriberBufferSize = size
}
}
// WithAddSubscriberTimeout sets the timeout for adding a subscriber
// If it is not possible to add a subscriber within the timeout,
// it means the fanout is at capacity, and it is better to reject the subscriber,
// that will likely be propagated to the downstream,
// which would retry and eventually succeed with another instance
func WithAddSubscriberTimeout(timeout time.Duration) Option {
if timeout < minAddSubscriberTimeout {
timeout = minAddSubscriberTimeout
}
return func(c *config) {
c.addSubscriberTimeout = timeout
}
}
func defaultFanOutConfig() config {
var c config
c.apply(
WithPublishTimeout(defaultPublishTimeout),
WithMessagesBufferSize(defaultMessageBufferSize),
WithReceiverCallbackTimeout(defaultReceiverCallbackTimeout),
WithReceiverBufferSize(defaultReceiverBufferSize),
WithSubscriberBufferSize(defaultSubscriberBufferSize),
WithAddSubscriberTimeout(defaultAddSubscriberTimeout),
)
return c
}
func (c *config) apply(opts ...Option) {
for _, opt := range opts {
opt(c)
}
}

70
pkg/fanout/fanout.go Normal file
View file

@ -0,0 +1,70 @@
// Package fanout implements a fan-out pattern that allows publishing messages to multiple subscribers
package fanout
import (
"context"
"errors"
)
var (
// ErrSubscriberClosed is returned when a subscriber is closed on the subscriber side (Receive)
ErrSubscriberClosed = errors.New("subscriber closed")
// ErrSubscriberEvicted is returned when a subscriber is unable to keep up with the messages
ErrSubscriberEvicted = errors.New("subscriber evicted, cannot keep up consuming messages")
// ErrStopped is returned when the fanout is stopped
ErrStopped = errors.New("fanout is stopped, no more messages will be accepted")
)
// FanOut is a fan-out pattern implementation that allows publishing messages to multiple subscribers
type FanOut[T any] struct {
cfg config
done <-chan struct{}
messages chan T
subscribers chan *subscriber[T]
}
// Start creates and runs a new FanOut
func Start[T any](ctx context.Context, opts ...Option) *FanOut[T] {
cfg := defaultFanOutConfig()
cfg.apply(opts...)
f := &FanOut[T]{
cfg: cfg,
done: ctx.Done(),
messages: make(chan T, cfg.publishBufferSize),
subscribers: make(chan *subscriber[T], cfg.subscriberBufferSize),
}
go f.dispatchLoop(ctx)
return f
}
func (f *FanOut[T]) dispatchLoop(ctx context.Context) {
subscribers := make(subscribers[T])
defer subscribers.closeAll(ErrStopped)
for {
select {
case <-ctx.Done():
return
case sub := <-f.subscribers:
subscribers.add(sub)
continue
case msg := <-f.messages:
subscribers.dispatch(ctx, msg)
}
}
}
func (f *FanOut[T]) addSubscriber(ctx context.Context, sub *subscriber[T]) error {
ctx, cancel := context.WithTimeout(ctx, f.cfg.addSubscriberTimeout)
defer cancel()
select {
case <-ctx.Done():
return context.Cause(ctx)
case <-f.done:
return ErrStopped
case f.subscribers <- sub:
return nil
}
}

182
pkg/fanout/fanout_test.go Normal file
View file

@ -0,0 +1,182 @@
package fanout_test
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
"github.com/pomerium/pomerium/pkg/fanout"
)
func TestFanOutStopped(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
cancel()
f := fanout.Start[int](ctx, fanout.WithPublishTimeout(time.Millisecond*10))
assert.Eventually(t, func() bool {
return errors.Is(f.Publish(context.Background(), 1), fanout.ErrStopped)
}, 5*time.Second, 10*time.Millisecond)
err := f.Receive(context.Background(), func(ctx context.Context, msg int) error {
return nil
})
assert.ErrorIs(t, err, fanout.ErrStopped)
}
func TestFanOutEvictSlowSubscriber(t *testing.T) {
t.Parallel()
timeout := time.Second * 5
ctx, cancel := context.WithTimeout(context.Background(), timeout)
t.Cleanup(cancel)
f := fanout.Start[int](ctx,
fanout.WithReceiverBufferSize(1),
fanout.WithReceiverCallbackTimeout(timeout),
)
subscriberAdded := make(chan struct{})
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
err := f.Receive(ctx, func(ctx context.Context, msg int) error {
select {
case <-ctx.Done():
// context was canceled as expected
// when the subscriber was evicted
case <-time.After(timeout / 2):
t.Error("receiver context was not canceled")
}
return nil
}, fanout.WithOnSubscriberAdded[int](func() {
close(subscriberAdded)
}))
assert.ErrorIs(t, err, fanout.ErrSubscriberEvicted, "expect explicit error indicating subscriber eviction")
return nil
})
eg.Go(func() error {
select {
case <-ctx.Done():
return fmt.Errorf("timed out waiting for subscriber: %w", ctx.Err())
case <-subscriberAdded:
}
// this message will be consumed by the subscriber above, which will block in the callback
assert.NoError(t, f.Publish(ctx, 1))
// this message will get into fanout-receiver's buffer as the subscriber is blocked
assert.NoError(t, f.Publish(ctx, 1))
// this messsage will cause the subscriber to be evicted as all buffers are full
assert.NoError(t, f.Publish(ctx, 1))
return nil
})
require.NoError(t, eg.Wait())
}
func TestFanOutReceiverCancelOnError(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
f := fanout.Start[int](ctx)
receiverErr := errors.New("receiver error")
errch := make(chan error, 1)
ready := make(chan struct{})
go func() {
errch <- f.Receive(ctx, func(ctx context.Context, msg int) error {
return receiverErr
}, fanout.WithOnSubscriberAdded[int](func() { close(ready) }))
}()
<-ready
require.NoError(t, f.Publish(ctx, 1))
assert.ErrorIs(t, <-errch, receiverErr)
}
func TestFanOutFilter(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
t.Cleanup(cancel)
f := fanout.Start[int](ctx)
ready := make(chan struct{})
results := make(chan int)
go func() {
_ = f.Receive(ctx, func(ctx context.Context, msg int) error {
results <- msg
return nil
},
fanout.WithFilter(func(msg int) bool { return msg%2 == 0 }),
fanout.WithOnSubscriberAdded[int](func() { close(ready) }),
)
}()
<-ready
t.Log("ready to publish")
for i := 0; i < 10; i++ {
assert.NoError(t, f.Publish(ctx, i))
}
t.Log("published all messages")
for i := 0; i < 9; i += 2 {
assert.Equal(t, i, <-results)
}
}
func BenchmarkFanout(b *testing.B) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
b.Cleanup(cancel)
cycles := 1
f := fanout.Start[int](ctx)
errStopReceiver := errors.New("stop receiver")
eg, ctx := errgroup.WithContext(ctx)
eg.SetLimit(-1)
ready := make(chan struct{}, b.N)
for i := 0; i < b.N; i++ {
want := i
eg.Go(func() error {
seen := 0
err := f.Receive(ctx, func(ctx context.Context, _ int) error {
if seen++; seen == cycles {
return errStopReceiver
}
return nil
},
fanout.WithOnSubscriberAdded[int](func() { ready <- struct{}{} }),
fanout.WithFilter(func(msg int) bool { return msg == want }),
)
if !errors.Is(err, errStopReceiver) && !errors.Is(err, context.Canceled) {
b.Error(err)
return err
}
return nil
})
}
eg.Go(func() error {
for i := 0; i < b.N; i++ {
<-ready
}
for c := 0; c < cycles; c++ {
for i := 0; i < b.N; i++ {
err := f.Publish(ctx, i)
if err != nil {
b.Error(err)
return err
}
}
}
return nil
})
require.NoError(b, eg.Wait())
}

19
pkg/fanout/publish.go Normal file
View file

@ -0,0 +1,19 @@
package fanout
import "context"
// Publish publishes a message to all currently registered subscribers
// if the fanout is closed, ErrStopped is returned
func (f *FanOut[T]) Publish(ctx context.Context, msg T) error {
ctx, cancel := context.WithTimeout(ctx, f.cfg.publishTimeout)
defer cancel()
select {
case <-ctx.Done():
return ctx.Err()
case <-f.done:
return ErrStopped
case f.messages <- msg:
return nil
}
}

106
pkg/fanout/receive.go Normal file
View file

@ -0,0 +1,106 @@
package fanout
import (
"context"
"fmt"
"time"
)
// ReceiverCallback is the callback function that is called for each message received
// if an error is returned, Receive will return immediately with that error, closing the subscriber
type ReceiverCallback[T any] func(ctx context.Context, msg T) error
// ReceiveOption is an option for receiver
type ReceiveOption[T any] func(*subscriber[T])
// WithFilter returns a ReceiveOption that filters messages for the subscriber
// if the filter returns false, the message is not sent to the subscriber
// this function is called for each message received and subsequently for each subscriber
// and should not be computationally expensive or block
func WithFilter[T any](filter func(T) bool) ReceiveOption[T] {
return func(sub *subscriber[T]) {
sub.filter = filter
}
}
// WithOnSubscriberAdded should only be used for tests
func WithOnSubscriberAdded[T any](onAdded func()) ReceiveOption[T] {
return func(sub *subscriber[T]) {
sub.onAdded = onAdded
}
}
// Receive subscribes to receive messages until the context is canceled or an error occurs
// onMessage is called for each message received.
// if an error is returned, Receive will return immediately
func (f *FanOut[T]) Receive(ctx context.Context, onMessage ReceiverCallback[T], opts ...ReceiveOption[T]) error {
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
messages := make(chan T, f.cfg.receiverBufferSize)
sub := newSubscriber[T](messages, f.done, cancel, opts...)
err := f.addSubscriber(ctx, sub)
if err != nil {
return fmt.Errorf("add subscriber: %w", err)
}
err = f.receiveLoop(ctx, messages, onMessage)
if err != nil {
return fmt.Errorf("receive: %w", err)
}
return nil
}
func newSubscriber[T any](
messages chan<- T,
done <-chan struct{},
cancel context.CancelCauseFunc,
opts ...ReceiveOption[T],
) *subscriber[T] {
sub := &subscriber[T]{
messages: messages,
done: done,
cancel: cancel,
}
for _, opt := range opts {
opt(sub)
}
return sub
}
func (f *FanOut[T]) receiveLoop(
ctx context.Context,
messages <-chan T,
onMessage ReceiverCallback[T],
) error {
for {
select {
case <-ctx.Done():
return context.Cause(ctx)
case <-f.done:
return ErrStopped
case msg, ok := <-messages:
if !ok {
return ErrSubscriberEvicted
}
err := callWithTimeout(ctx, f.cfg.receiverCallbackTimeout, onMessage, msg)
if err != nil {
return fmt.Errorf("onMessage callback: %w", err)
}
}
}
}
func callWithTimeout[T any](
ctx context.Context,
timeout time.Duration,
cb ReceiverCallback[T],
msg T,
) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return cb(ctx, msg)
}

65
pkg/fanout/subscribers.go Normal file
View file

@ -0,0 +1,65 @@
package fanout
import (
"context"
)
// subscriber represents an individual subscriber to a fanout
type subscriber[T any] struct {
// messages is the channel that the subscriber receives messages on
messages chan<- T
// done is closed when the subscriber is closed
// it is used to signal the dispatchLoop that the subscriber is closed and should be removed
done <-chan struct{}
// cancel is passed so that dispatchLoop can cancel the subscriber if it discards it from its side
cancel context.CancelCauseFunc
// filter identifies the messages that the subscriber is interested in.
filter func(T) bool
// onAdded is called when the subscriber is added to the fanout
// it is only used for tests
onAdded func()
}
// subscriberCloseFn is a function that closes a subscriber and propagates an error to it
type subscriberCloseFn func(err error)
// subscribers is a map of subscribers to their close functions
type subscribers[T any] map[*subscriber[T]]subscriberCloseFn
// closeAll closes all subscribers and propagates the given error to them
func (s subscribers[T]) closeAll(err error) {
for _, close := range s {
close(err)
}
}
// add adds subscriber to the fanout
func (s subscribers[T]) add(sub *subscriber[T]) {
s[sub] = func(err error) {
close(sub.messages)
sub.cancel(err)
delete(s, sub)
}
if sub.onAdded != nil {
sub.onAdded()
}
}
// dispatch dispatches the given message to all subscribers
func (s subscribers[T]) dispatch(ctx context.Context, msg T) {
for sub, close := range s {
if sub.filter != nil && !sub.filter(msg) {
continue
}
select {
case <-ctx.Done():
return
case sub.messages <- msg:
case <-sub.done:
close(ErrSubscriberClosed)
default:
close(ErrSubscriberEvicted)
}
}
}

123
pkg/zero/api.go Normal file
View file

@ -0,0 +1,123 @@
// Package zero contains the pomerium zero configuration API client
package zero
import (
"context"
"fmt"
"github.com/pomerium/pomerium/pkg/fanout"
"github.com/pomerium/pomerium/pkg/zero/apierror"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
connect_api "github.com/pomerium/pomerium/pkg/zero/connect"
connect_mux "github.com/pomerium/pomerium/pkg/zero/connect-mux"
token_api "github.com/pomerium/pomerium/pkg/zero/token"
)
// API is a Pomerium Zero Cluster API client
type API struct {
cfg *config
cluster cluster_api.ClientWithResponsesInterface
mux *connect_mux.Mux
downloadURLCache *cluster_api.URLCache
}
// WatchOption defines which events to watch for
type WatchOption = connect_mux.WatchOption
// NewAPI creates a new API client
func NewAPI(ctx context.Context, opts ...Option) (*API, error) {
cfg, err := newConfig(opts...)
if err != nil {
return nil, err
}
fetcher, err := cluster_api.NewTokenFetcher(cfg.clusterAPIEndpoint,
cluster_api.WithHTTPClient(cfg.httpClient),
)
if err != nil {
return nil, fmt.Errorf("error creating token fetcher: %w", err)
}
tokenCache := token_api.NewCache(fetcher, cfg.apiToken)
clusterClient, err := cluster_api.NewAuthorizedClient(cfg.clusterAPIEndpoint, tokenCache.GetToken, cfg.httpClient)
if err != nil {
return nil, fmt.Errorf("error creating cluster client: %w", err)
}
connectClient, err := connect_api.NewAuthorizedConnectClient(ctx, cfg.connectAPIEndpoint, tokenCache.GetToken)
if err != nil {
return nil, fmt.Errorf("error creating connect client: %w", err)
}
return &API{
cfg: cfg,
cluster: clusterClient,
mux: connect_mux.New(connectClient),
downloadURLCache: cluster_api.NewURLCache(),
}, nil
}
// Connect connects to the connect API and allows watching for changes
func (api *API) Connect(ctx context.Context, opts ...fanout.Option) error {
return api.mux.Run(ctx, opts...)
}
// Watch dispatches API updates
func (api *API) Watch(ctx context.Context, opts ...WatchOption) error {
return api.mux.Watch(ctx, opts...)
}
// GetClusterBootstrapConfig fetches the bootstrap configuration from the cluster API
func (api *API) GetClusterBootstrapConfig(ctx context.Context) (*cluster_api.BootstrapConfig, error) {
return apierror.CheckResponse[cluster_api.BootstrapConfig](
api.cluster.GetClusterBootstrapConfigWithResponse(ctx),
)
}
// GetClusterResourceBundles fetches the resource bundles from the cluster API
func (api *API) GetClusterResourceBundles(ctx context.Context) (*cluster_api.GetBundlesResponse, error) {
return apierror.CheckResponse[cluster_api.GetBundlesResponse](
api.cluster.GetClusterResourceBundlesWithResponse(ctx),
)
}
// ReportBundleAppliedSuccess reports a successful bundle application
func (api *API) ReportBundleAppliedSuccess(ctx context.Context, bundleID string, metadata map[string]string) error {
status := cluster_api.BundleStatus{
Success: &cluster_api.BundleStatusSuccess{
Metadata: metadata,
},
}
_, err := apierror.CheckResponse[cluster_api.EmptyResponse](
api.cluster.ReportClusterResourceBundleStatusWithResponse(ctx, bundleID, status),
)
if err != nil {
return fmt.Errorf("error reporting bundle status: %w", err)
}
return err
}
// ReportBundleAppliedFailure reports a failed bundle application
func (api *API) ReportBundleAppliedFailure(
ctx context.Context,
bundleID string,
source cluster_api.BundleStatusFailureSource,
err error,
) error {
status := cluster_api.BundleStatus{
Failure: &cluster_api.BundleStatusFailure{
Message: err.Error(),
Source: source,
},
}
_, err = apierror.CheckResponse[cluster_api.EmptyResponse](
api.cluster.ReportClusterResourceBundleStatusWithResponse(ctx, bundleID, status),
)
if err != nil {
return fmt.Errorf("error reporting bundle status: %w", err)
}
return err
}

View file

@ -0,0 +1,42 @@
package apierror
import (
"fmt"
"net/http"
)
// RequestIDError is an error that wraps another error and includes the response ID
type RequestIDError struct {
Err error
ResponseID *string
}
// Error implements error for RequestIDError
func (e *RequestIDError) Error() string {
if e.ResponseID == nil {
return e.Err.Error()
}
return fmt.Sprintf("[x-response-id:%s]: %v", *e.ResponseID, e.Err)
}
// Unwrap implements errors.Unwrap for RequestIDError
func (e *RequestIDError) Unwrap() error {
return e.Err
}
// Is implements errors.Is for RequestIDError
func (e *RequestIDError) Is(err error) bool {
//nolint:errorlint
_, ok := err.(*RequestIDError)
return ok
}
// WithRequestID creates a new RequestIDError
func WithRequestID(err error, headers http.Header) *RequestIDError {
r := &RequestIDError{Err: err}
id := headers.Get("X-Response-Id")
if id != "" {
r.ResponseID = &id
}
return r
}

View file

@ -0,0 +1,56 @@
// Package apierror provides a consistent way to handle errors from API calls
package apierror
import (
"fmt"
"net/http"
)
// CheckResponse checks the response for errors and returns the value or an error
func CheckResponse[T any](resp APIResponse[T], err error) (*T, error) {
if err != nil {
return nil, err
}
value := resp.GetValue()
if value != nil {
return value, nil
}
//nolint:bodyclose
return nil, WithRequestID(responseError(resp), resp.GetHTTPResponse().Header)
}
// APIResponse is the interface that wraps the response from an API call
type APIResponse[T any] interface {
// GetHTTPResponse returns the HTTP response
GetHTTPResponse() *http.Response
// GetInternalServerError returns the internal server error
GetInternalServerError() (string, bool)
// GetBadRequestError returns the bad request error
GetBadRequestError() (string, bool)
// GetValue returns the value
GetValue() *T
}
// Error is the interface that wraps the error returned from an API call
type Error interface {
GetError() string
}
func responseError[T any](resp APIResponse[T]) error {
reason, ok := resp.GetBadRequestError()
if ok {
return NewTerminalError(fmt.Errorf("bad request: %v", reason))
}
reason, ok = resp.GetInternalServerError()
if ok {
return fmt.Errorf("internal server error: %v", reason)
}
//nolint:bodyclose
httpResp := resp.GetHTTPResponse()
if httpResp == nil {
return fmt.Errorf("unexpected response: nil")
}
return fmt.Errorf("unexpected response: %v", httpResp.StatusCode)
}

View file

@ -0,0 +1,40 @@
package apierror_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/pkg/zero/apierror"
"github.com/pomerium/pomerium/pkg/zero/cluster"
)
func TestResponse(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
err error
response apierror.APIResponse[cluster.ExchangeTokenResponse]
wantVal *cluster.ExchangeTokenResponse
wantErr error
}{
{
name: "success",
response: &cluster.ExchangeClusterIdentityTokenResp{
HTTPResponse: &http.Response{},
JSON200: &cluster.ExchangeTokenResponse{},
},
err: nil,
wantVal: &cluster.ExchangeTokenResponse{},
wantErr: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
gotVal, gotErr := apierror.CheckResponse(tc.response, tc.err)
assert.Equal(t, tc.wantVal, gotVal)
assert.Equal(t, tc.wantErr, gotErr)
})
}
}

View file

@ -0,0 +1,44 @@
package apierror
import (
"errors"
"fmt"
)
// terminalError is an error that should not be retried
type terminalError struct {
Err error
}
// Error implements error for terminalError
func (e *terminalError) Error() string {
return fmt.Sprintf("terminal error: %v", e.Err)
}
// Unwrap implements errors.Unwrap for terminalError
func (e *terminalError) Unwrap() error {
return e.Err
}
// Is implements errors.Is for terminalError
func (e *terminalError) Is(err error) bool {
//nolint:errorlint
_, ok := err.(*terminalError)
return ok
}
func (e *terminalError) IsTerminal() {}
// NewTerminalError creates a new terminal error that cannot be retried
func NewTerminalError(err error) error {
return &terminalError{Err: err}
}
// IsTerminalError returns true if the error is a terminal error
func IsTerminalError(err error) bool {
if err == nil {
return false
}
var te *terminalError
return errors.As(err, &te)
}

View file

@ -0,0 +1,813 @@
// Package cluster provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
package cluster
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/oapi-codegen/runtime"
)
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
// The standard http.Client implements this interface.
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
// The endpoint of the server conforming to this interface, with scheme,
// https://api.deepmap.com for example. This can contain a path relative
// to the server, such as https://api.deepmap.com/dev-test, and all the
// paths in the swagger spec will be appended to the server.
Server string
// Doer for performing requests, typically a *http.Client with any
// customized settings, such as certificate chains.
Client HttpRequestDoer
// A list of callbacks for modifying requests which are generated before sending over
// the network.
RequestEditors []RequestEditorFn
}
// ClientOption allows setting custom parameters during construction
type ClientOption func(*Client) error
// Creates a new Client, with reasonable defaults
func NewClient(server string, opts ...ClientOption) (*Client, error) {
// create a client with sane default values
client := Client{
Server: server,
}
// mutate client and add all optional params
for _, o := range opts {
if err := o(&client); err != nil {
return nil, err
}
}
// ensure the server URL always has a trailing slash
if !strings.HasSuffix(client.Server, "/") {
client.Server += "/"
}
// create httpClient, if not already present
if client.Client == nil {
client.Client = &http.Client{}
}
return &client, nil
}
// WithHTTPClient allows overriding the default Doer, which is
// automatically created using http.Client. This is useful for tests.
func WithHTTPClient(doer HttpRequestDoer) ClientOption {
return func(c *Client) error {
c.Client = doer
return nil
}
}
// WithRequestEditorFn allows setting up a callback function, which will be
// called right before sending the request. This can be used to mutate the request.
func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
return func(c *Client) error {
c.RequestEditors = append(c.RequestEditors, fn)
return nil
}
}
// The interface specification for the client above.
type ClientInterface interface {
// GetClusterBootstrapConfig request
GetClusterBootstrapConfig(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetClusterResourceBundles request
GetClusterResourceBundles(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
// DownloadClusterResourceBundle request
DownloadClusterResourceBundle(ctx context.Context, bundleId BundleId, reqEditors ...RequestEditorFn) (*http.Response, error)
// ReportClusterResourceBundleStatusWithBody request with any body
ReportClusterResourceBundleStatusWithBody(ctx context.Context, bundleId BundleId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
ReportClusterResourceBundleStatus(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// ExchangeClusterIdentityTokenWithBody request with any body
ExchangeClusterIdentityTokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
ExchangeClusterIdentityToken(ctx context.Context, body ExchangeClusterIdentityTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) GetClusterBootstrapConfig(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetClusterBootstrapConfigRequest(c.Server)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) GetClusterResourceBundles(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetClusterResourceBundlesRequest(c.Server)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) DownloadClusterResourceBundle(ctx context.Context, bundleId BundleId, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewDownloadClusterResourceBundleRequest(c.Server, bundleId)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) ReportClusterResourceBundleStatusWithBody(ctx context.Context, bundleId BundleId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewReportClusterResourceBundleStatusRequestWithBody(c.Server, bundleId, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) ReportClusterResourceBundleStatus(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewReportClusterResourceBundleStatusRequest(c.Server, bundleId, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) ExchangeClusterIdentityTokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewExchangeClusterIdentityTokenRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) ExchangeClusterIdentityToken(ctx context.Context, body ExchangeClusterIdentityTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewExchangeClusterIdentityTokenRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
// NewGetClusterBootstrapConfigRequest generates requests for GetClusterBootstrapConfig
func NewGetClusterBootstrapConfigRequest(server string) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/bootstrap")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewGetClusterResourceBundlesRequest generates requests for GetClusterResourceBundles
func NewGetClusterResourceBundlesRequest(server string) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/bundles")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewDownloadClusterResourceBundleRequest generates requests for DownloadClusterResourceBundle
func NewDownloadClusterResourceBundleRequest(server string, bundleId BundleId) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "bundleId", runtime.ParamLocationPath, bundleId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/bundles/%s/download", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewReportClusterResourceBundleStatusRequest calls the generic ReportClusterResourceBundleStatus builder with application/json body
func NewReportClusterResourceBundleStatusRequest(server string, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewReportClusterResourceBundleStatusRequestWithBody(server, bundleId, "application/json", bodyReader)
}
// NewReportClusterResourceBundleStatusRequestWithBody generates requests for ReportClusterResourceBundleStatus with any type of body
func NewReportClusterResourceBundleStatusRequestWithBody(server string, bundleId BundleId, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "bundleId", runtime.ParamLocationPath, bundleId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/bundles/%s/status", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewExchangeClusterIdentityTokenRequest calls the generic ExchangeClusterIdentityToken builder with application/json body
func NewExchangeClusterIdentityTokenRequest(server string, body ExchangeClusterIdentityTokenJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewExchangeClusterIdentityTokenRequestWithBody(server, "application/json", bodyReader)
}
// NewExchangeClusterIdentityTokenRequestWithBody generates requests for ExchangeClusterIdentityToken with any type of body
func NewExchangeClusterIdentityTokenRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/exchangeToken")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
return err
}
}
for _, r := range additionalEditors {
if err := r(ctx, req); err != nil {
return err
}
}
return nil
}
// ClientWithResponses builds on ClientInterface to offer response payloads
type ClientWithResponses struct {
ClientInterface
}
// NewClientWithResponses creates a new ClientWithResponses, which wraps
// Client with return type handling
func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
client, err := NewClient(server, opts...)
if err != nil {
return nil, err
}
return &ClientWithResponses{client}, nil
}
// WithBaseURL overrides the baseURL.
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) error {
newBaseURL, err := url.Parse(baseURL)
if err != nil {
return err
}
c.Server = newBaseURL.String()
return nil
}
}
// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
// GetClusterBootstrapConfigWithResponse request
GetClusterBootstrapConfigWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClusterBootstrapConfigResp, error)
// GetClusterResourceBundlesWithResponse request
GetClusterResourceBundlesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClusterResourceBundlesResp, error)
// DownloadClusterResourceBundleWithResponse request
DownloadClusterResourceBundleWithResponse(ctx context.Context, bundleId BundleId, reqEditors ...RequestEditorFn) (*DownloadClusterResourceBundleResp, error)
// ReportClusterResourceBundleStatusWithBodyWithResponse request with any body
ReportClusterResourceBundleStatusWithBodyWithResponse(ctx context.Context, bundleId BundleId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReportClusterResourceBundleStatusResp, error)
ReportClusterResourceBundleStatusWithResponse(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*ReportClusterResourceBundleStatusResp, error)
// ExchangeClusterIdentityTokenWithBodyWithResponse request with any body
ExchangeClusterIdentityTokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error)
ExchangeClusterIdentityTokenWithResponse(ctx context.Context, body ExchangeClusterIdentityTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error)
}
type GetClusterBootstrapConfigResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *GetBootstrapConfigResponse
JSON400 *ErrorResponse
JSON500 *ErrorResponse
}
// Status returns HTTPResponse.Status
func (r GetClusterBootstrapConfigResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r GetClusterBootstrapConfigResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type GetClusterResourceBundlesResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *GetBundlesResponse
JSON400 *ErrorResponse
JSON500 *ErrorResponse
}
// Status returns HTTPResponse.Status
func (r GetClusterResourceBundlesResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r GetClusterResourceBundlesResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type DownloadClusterResourceBundleResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *DownloadBundleResponse
JSON400 *ErrorResponse
JSON404 *ErrorResponse
JSON500 *ErrorResponse
}
// Status returns HTTPResponse.Status
func (r DownloadClusterResourceBundleResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r DownloadClusterResourceBundleResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type ReportClusterResourceBundleStatusResp struct {
Body []byte
HTTPResponse *http.Response
JSON400 *ErrorResponse
JSON500 *ErrorResponse
}
// Status returns HTTPResponse.Status
func (r ReportClusterResourceBundleStatusResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r ReportClusterResourceBundleStatusResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type ExchangeClusterIdentityTokenResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ExchangeTokenResponse
JSON400 *ErrorResponse
JSON500 *ErrorResponse
}
// Status returns HTTPResponse.Status
func (r ExchangeClusterIdentityTokenResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r ExchangeClusterIdentityTokenResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// GetClusterBootstrapConfigWithResponse request returning *GetClusterBootstrapConfigResp
func (c *ClientWithResponses) GetClusterBootstrapConfigWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClusterBootstrapConfigResp, error) {
rsp, err := c.GetClusterBootstrapConfig(ctx, reqEditors...)
if err != nil {
return nil, err
}
return ParseGetClusterBootstrapConfigResp(rsp)
}
// GetClusterResourceBundlesWithResponse request returning *GetClusterResourceBundlesResp
func (c *ClientWithResponses) GetClusterResourceBundlesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetClusterResourceBundlesResp, error) {
rsp, err := c.GetClusterResourceBundles(ctx, reqEditors...)
if err != nil {
return nil, err
}
return ParseGetClusterResourceBundlesResp(rsp)
}
// DownloadClusterResourceBundleWithResponse request returning *DownloadClusterResourceBundleResp
func (c *ClientWithResponses) DownloadClusterResourceBundleWithResponse(ctx context.Context, bundleId BundleId, reqEditors ...RequestEditorFn) (*DownloadClusterResourceBundleResp, error) {
rsp, err := c.DownloadClusterResourceBundle(ctx, bundleId, reqEditors...)
if err != nil {
return nil, err
}
return ParseDownloadClusterResourceBundleResp(rsp)
}
// ReportClusterResourceBundleStatusWithBodyWithResponse request with arbitrary body returning *ReportClusterResourceBundleStatusResp
func (c *ClientWithResponses) ReportClusterResourceBundleStatusWithBodyWithResponse(ctx context.Context, bundleId BundleId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReportClusterResourceBundleStatusResp, error) {
rsp, err := c.ReportClusterResourceBundleStatusWithBody(ctx, bundleId, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseReportClusterResourceBundleStatusResp(rsp)
}
func (c *ClientWithResponses) ReportClusterResourceBundleStatusWithResponse(ctx context.Context, bundleId BundleId, body ReportClusterResourceBundleStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*ReportClusterResourceBundleStatusResp, error) {
rsp, err := c.ReportClusterResourceBundleStatus(ctx, bundleId, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseReportClusterResourceBundleStatusResp(rsp)
}
// ExchangeClusterIdentityTokenWithBodyWithResponse request with arbitrary body returning *ExchangeClusterIdentityTokenResp
func (c *ClientWithResponses) ExchangeClusterIdentityTokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error) {
rsp, err := c.ExchangeClusterIdentityTokenWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseExchangeClusterIdentityTokenResp(rsp)
}
func (c *ClientWithResponses) ExchangeClusterIdentityTokenWithResponse(ctx context.Context, body ExchangeClusterIdentityTokenJSONRequestBody, reqEditors ...RequestEditorFn) (*ExchangeClusterIdentityTokenResp, error) {
rsp, err := c.ExchangeClusterIdentityToken(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseExchangeClusterIdentityTokenResp(rsp)
}
// ParseGetClusterBootstrapConfigResp parses an HTTP response from a GetClusterBootstrapConfigWithResponse call
func ParseGetClusterBootstrapConfigResp(rsp *http.Response) (*GetClusterBootstrapConfigResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &GetClusterBootstrapConfigResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest GetBootstrapConfigResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}
// ParseGetClusterResourceBundlesResp parses an HTTP response from a GetClusterResourceBundlesWithResponse call
func ParseGetClusterResourceBundlesResp(rsp *http.Response) (*GetClusterResourceBundlesResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &GetClusterResourceBundlesResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest GetBundlesResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}
// ParseDownloadClusterResourceBundleResp parses an HTTP response from a DownloadClusterResourceBundleWithResponse call
func ParseDownloadClusterResourceBundleResp(rsp *http.Response) (*DownloadClusterResourceBundleResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &DownloadClusterResourceBundleResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest DownloadBundleResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON404 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}
// ParseReportClusterResourceBundleStatusResp parses an HTTP response from a ReportClusterResourceBundleStatusWithResponse call
func ParseReportClusterResourceBundleStatusResp(rsp *http.Response) (*ReportClusterResourceBundleStatusResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &ReportClusterResourceBundleStatusResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}
// ParseExchangeClusterIdentityTokenResp parses an HTTP response from a ExchangeClusterIdentityTokenWithResponse call
func ParseExchangeClusterIdentityTokenResp(rsp *http.Response) (*ExchangeClusterIdentityTokenResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &ExchangeClusterIdentityTokenResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest ExchangeTokenResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ErrorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}

View file

@ -0,0 +1,49 @@
// Package cluster is an API client for the cluster service
package cluster
import (
"context"
"fmt"
"net/http"
"time"
)
const (
defaultMinTokenTTL = time.Minute * 5
)
type client struct {
tokenProvider TokenProviderFn
httpClient *http.Client
minTokenTTL time.Duration
}
// TokenProviderFn is a function that returns a token that is expected to be valid for at least minTTL
type TokenProviderFn func(ctx context.Context, minTTL time.Duration) (string, error)
// NewAuthorizedClient creates a new HTTP client that will automatically add an authorization header
func NewAuthorizedClient(
endpoint string,
tokenProvider TokenProviderFn,
httpClient *http.Client,
) (ClientWithResponsesInterface, error) {
c := &client{
minTokenTTL: defaultMinTokenTTL,
httpClient: httpClient,
}
c.tokenProvider = tokenProvider
return NewClientWithResponses(endpoint, WithHTTPClient(c))
}
func (c *client) Do(req *http.Request) (*http.Response, error) {
ctx := req.Context()
token, err := c.tokenProvider(ctx, c.minTokenTTL)
if err != nil {
return nil, fmt.Errorf("error getting token: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
return c.httpClient.Do(req)
}

View file

@ -0,0 +1,10 @@
package: cluster
generate:
client: true
models: false
output: client.gen.go
output-options:
skip-prune: true
# We use Response suffix internally throughout the response objects,
# that conflicts with generated client
response-type-suffix: Resp

View file

@ -0,0 +1,148 @@
package cluster
import (
"net/http"
"github.com/pomerium/pomerium/pkg/zero/apierror"
)
// EmptyResponse is an empty response
type EmptyResponse struct{}
var (
_ apierror.APIResponse[ExchangeTokenResponse] = (*ExchangeClusterIdentityTokenResp)(nil)
_ apierror.APIResponse[BootstrapConfig] = (*GetClusterBootstrapConfigResp)(nil)
_ apierror.APIResponse[GetBundlesResponse] = (*GetClusterResourceBundlesResp)(nil)
_ apierror.APIResponse[DownloadBundleResponse] = (*DownloadClusterResourceBundleResp)(nil)
_ apierror.APIResponse[EmptyResponse] = (*ReportClusterResourceBundleStatusResp)(nil)
)
// GetBadRequestError implements apierror.APIResponse
func (r *ExchangeClusterIdentityTokenResp) GetBadRequestError() (string, bool) {
if r.JSON400 == nil {
return "", false
}
return r.JSON400.Error, true
}
// GetInternalServerError implements apierror.APIResponse
func (r *ExchangeClusterIdentityTokenResp) GetInternalServerError() (string, bool) {
if r.JSON500 == nil {
return "", false
}
return r.JSON500.Error, true
}
// GetValue implements apierror.APIResponse
func (r *ExchangeClusterIdentityTokenResp) GetValue() *ExchangeTokenResponse {
return r.JSON200
}
// GetHTTPResponse implements apierror.APIResponse
func (r *ExchangeClusterIdentityTokenResp) GetHTTPResponse() *http.Response {
return r.HTTPResponse
}
// GetBadRequestError implements apierror.APIResponse
func (r *GetClusterBootstrapConfigResp) GetBadRequestError() (string, bool) {
if r.JSON400 == nil {
return "", false
}
return r.JSON400.Error, true
}
// GetInternalServerError implements apierror.APIResponse
func (r *GetClusterBootstrapConfigResp) GetInternalServerError() (string, bool) {
if r.JSON500 == nil {
return "", false
}
return r.JSON500.Error, true
}
// GetValue implements apierror.APIResponse
func (r *GetClusterBootstrapConfigResp) GetValue() *BootstrapConfig {
return r.JSON200
}
// GetHTTPResponse implements apierror.APIResponse
func (r *GetClusterBootstrapConfigResp) GetHTTPResponse() *http.Response {
return r.HTTPResponse
}
// GetBadRequestError implements apierror.APIResponse
func (r *GetClusterResourceBundlesResp) GetBadRequestError() (string, bool) {
if r.JSON400 == nil {
return "", false
}
return r.JSON400.Error, true
}
// GetInternalServerError implements apierror.APIResponse
func (r *GetClusterResourceBundlesResp) GetInternalServerError() (string, bool) {
if r.JSON500 == nil {
return "", false
}
return r.JSON500.Error, true
}
// GetValue implements apierror.APIResponse
func (r *GetClusterResourceBundlesResp) GetValue() *GetBundlesResponse {
return r.JSON200
}
// GetHTTPResponse implements apierror.APIResponse
func (r *GetClusterResourceBundlesResp) GetHTTPResponse() *http.Response {
return r.HTTPResponse
}
// GetBadRequestError implements apierror.APIResponse
func (r *DownloadClusterResourceBundleResp) GetBadRequestError() (string, bool) {
if r.JSON400 == nil {
return "", false
}
return r.JSON400.Error, true
}
// GetInternalServerError implements apierror.APIResponse
func (r *DownloadClusterResourceBundleResp) GetInternalServerError() (string, bool) {
if r.JSON500 == nil {
return "", false
}
return r.JSON500.Error, true
}
// GetValue implements apierror.APIResponse
func (r *DownloadClusterResourceBundleResp) GetValue() *DownloadBundleResponse {
return r.JSON200
}
// GetHTTPResponse implements apierror.APIResponse
func (r *DownloadClusterResourceBundleResp) GetHTTPResponse() *http.Response {
return r.HTTPResponse
}
// GetBadRequestError implements apierror.APIResponse
func (r *ReportClusterResourceBundleStatusResp) GetBadRequestError() (string, bool) {
if r.JSON400 == nil {
return "", false
}
return r.JSON400.Error, true
}
// GetInternalServerError implements apierror.APIResponse
func (r *ReportClusterResourceBundleStatusResp) GetInternalServerError() (string, bool) {
if r.JSON500 == nil {
return "", false
}
return r.JSON500.Error, true
}
// GetValue implements apierror.APIResponse
func (r *ReportClusterResourceBundleStatusResp) GetValue() *EmptyResponse {
return &EmptyResponse{}
}
// GetHTTPResponse implements apierror.APIResponse
func (r *ReportClusterResourceBundleStatusResp) GetHTTPResponse() *http.Response {
return r.HTTPResponse
}

View file

@ -0,0 +1,59 @@
package cluster_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
api "github.com/pomerium/pomerium/pkg/zero/cluster"
"github.com/pomerium/pomerium/pkg/zero/token"
)
func TestAPIClient(t *testing.T) {
t.Parallel()
respond := func(w http.ResponseWriter, status int, body any) {
t.Helper()
data, err := json.Marshal(body)
require.NoError(t, err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(data)
require.NoError(t, err)
}
idToken := "id-token"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/exchangeToken":
respond(w, http.StatusOK, api.ExchangeTokenResponse{
IdToken: idToken,
ExpiresInSeconds: "3600",
})
default:
t.Error("unexpected request", r.URL.Path)
}
}))
t.Cleanup(srv.Close)
fetcher, err := api.NewTokenFetcher(srv.URL)
require.NoError(t, err)
tokenCache := token.NewCache(fetcher, "refresh-token")
client, err := api.NewAuthorizedClient(srv.URL, tokenCache.GetToken, http.DefaultClient)
require.NoError(t, err)
resp, err := client.ExchangeClusterIdentityTokenWithResponse(context.Background(),
api.ExchangeTokenRequest{
RefreshToken: "refresh-token",
},
)
require.NoError(t, err)
require.Equal(t, idToken, resp.JSON200.IdToken)
}

View file

@ -0,0 +1,5 @@
package cluster
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=models.yaml openapi.yaml
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=server.yaml openapi.yaml
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=client.yaml openapi.yaml

View file

@ -0,0 +1,99 @@
// Package cluster provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
package cluster
const (
BearerAuthScopes = "bearerAuth.Scopes"
)
// Defines values for BundleStatusFailureSource.
const (
DatabrokerError BundleStatusFailureSource = "databroker_error"
DownloadError BundleStatusFailureSource = "download_error"
InvalidBundle BundleStatusFailureSource = "invalid_bundle"
IoError BundleStatusFailureSource = "io_error"
UnknownError BundleStatusFailureSource = "unknown_error"
)
// BootstrapConfig defines model for BootstrapConfig.
type BootstrapConfig struct {
// DatabrokerStorageConnection databroker storage connection string
DatabrokerStorageConnection *string `json:"databrokerStorageConnection,omitempty"`
}
// Bundle defines model for Bundle.
type Bundle struct {
// Id bundle id
Id string `json:"id"`
}
// BundleStatus defines model for BundleStatus.
type BundleStatus struct {
Failure *BundleStatusFailure `json:"failure,omitempty"`
Success *BundleStatusSuccess `json:"success,omitempty"`
}
// BundleStatusFailure defines model for BundleStatusFailure.
type BundleStatusFailure struct {
Message string `json:"message"`
// Source source of the failure
Source BundleStatusFailureSource `json:"source"`
}
// BundleStatusFailureSource source of the failure
type BundleStatusFailureSource string
// BundleStatusSuccess defines model for BundleStatusSuccess.
type BundleStatusSuccess struct {
// Metadata bundle metadata
Metadata map[string]string `json:"metadata"`
}
// DownloadBundleResponse defines model for DownloadBundleResponse.
type DownloadBundleResponse struct {
// CaptureMetadataHeaders bundle metadata that need be picked up by the client from the download URL
CaptureMetadataHeaders []string `json:"captureMetadataHeaders"`
ExpiresInSeconds string `json:"expiresInSeconds"`
// Url download URL
Url string `json:"url"`
}
// ErrorResponse defines model for ErrorResponse.
type ErrorResponse struct {
// Error Error message
Error string `json:"error"`
}
// ExchangeTokenRequest defines model for ExchangeTokenRequest.
type ExchangeTokenRequest struct {
// RefreshToken cluster identity token
RefreshToken string `json:"refreshToken"`
}
// ExchangeTokenResponse defines model for ExchangeTokenResponse.
type ExchangeTokenResponse struct {
ExpiresInSeconds string `json:"expiresInSeconds"`
// IdToken ID token
IdToken string `json:"idToken"`
}
// GetBootstrapConfigResponse defines model for GetBootstrapConfigResponse.
type GetBootstrapConfigResponse = BootstrapConfig
// GetBundlesResponse defines model for GetBundlesResponse.
type GetBundlesResponse struct {
Bundles []Bundle `json:"bundles"`
}
// BundleId defines model for bundleId.
type BundleId = string
// ReportClusterResourceBundleStatusJSONRequestBody defines body for ReportClusterResourceBundleStatus for application/json ContentType.
type ReportClusterResourceBundleStatusJSONRequestBody = BundleStatus
// ExchangeClusterIdentityTokenJSONRequestBody defines body for ExchangeClusterIdentityToken for application/json ContentType.
type ExchangeClusterIdentityTokenJSONRequestBody = ExchangeTokenRequest

View file

@ -0,0 +1,4 @@
package: cluster
generate:
models: true
output: models.gen.go

View file

@ -0,0 +1,269 @@
openapi: 3.0.0
info:
title: "Pomerium Zero SDK API"
version: 0.1.0
servers:
- url: /cluster/v1
security:
- bearerAuth: []
paths:
/bootstrap:
get:
description: Get cluster bootstrap configuration
operationId: getClusterBootstrapConfig
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/GetBootstrapConfigResponse"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/bundles:
get:
description: Get all cluster resource bundles
operationId: getClusterResourceBundles
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/GetBundlesResponse"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/bundles/{bundleId}/download:
get:
description: Download cluster resource bundle
operationId: downloadClusterResourceBundle
parameters:
- $ref: "#/components/parameters/bundleId"
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/DownloadBundleResponse"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: Not Found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/bundles/{bundleId}/status:
post:
description: Report cluster resource bundle status
operationId: reportClusterResourceBundleStatus
parameters:
- $ref: "#/components/parameters/bundleId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/BundleStatus"
responses:
"204":
description: OK
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/exchangeToken:
post:
description: Exchange cluster identity token for bearer token
operationId: exchangeClusterIdentityToken
tags: [token]
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExchangeTokenRequest"
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/ExchangeTokenResponse"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
components:
parameters:
bundleId:
name: bundleId
in: path
description: bundle id
required: true
schema:
type: string
schemas:
BootstrapConfig:
type: object
properties:
databrokerStorageConnection:
type: string
description: databroker storage connection string
Bundle:
type: object
properties:
id:
type: string
description: bundle id
required:
- id
BundleStatus:
type: object
properties:
success:
$ref: "#/components/schemas/BundleStatusSuccess"
failure:
$ref: "#/components/schemas/BundleStatusFailure"
BundleStatusSuccess:
type: object
properties:
metadata:
type: object
description: bundle metadata
additionalProperties:
type: string
required:
- metadata
BundleStatusFailure:
type: object
properties:
message:
type: string
source:
type: string
description: source of the failure
enum:
- download_error
- io_error
- invalid_bundle
- databroker_error
- unknown_error
required:
- source
- message
DownloadBundleResponse:
type: object
properties:
url:
type: string
description: download URL
expiresInSeconds:
type: string
format: int64
captureMetadataHeaders:
type: array
items:
type: string
description: bundle metadata that need be picked up by the client from the download URL
required:
- url
- expiresInSeconds
- captureMetadataHeaders
ErrorResponse:
type: object
properties:
error:
type: string
description: Error message
required:
- error
ExchangeTokenRequest:
type: object
properties:
refreshToken:
type: string
description: cluster identity token
required:
- refreshToken
ExchangeTokenResponse:
type: object
properties:
idToken:
type: string
description: ID token
expiresInSeconds:
type: string
format: int64
required:
- idToken
- expiresInSeconds
GetBootstrapConfigResponse:
$ref: "#/components/schemas/BootstrapConfig"
GetBundlesResponse:
type: object
properties:
bundles:
type: array
items:
$ref: "#/components/schemas/Bundle"
required:
- bundles

View file

@ -0,0 +1,677 @@
// Package cluster provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
package cluster
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/oapi-codegen/runtime"
strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"
)
// ServerInterface represents all server handlers.
type ServerInterface interface {
// (GET /bootstrap)
GetClusterBootstrapConfig(w http.ResponseWriter, r *http.Request)
// (GET /bundles)
GetClusterResourceBundles(w http.ResponseWriter, r *http.Request)
// (GET /bundles/{bundleId}/download)
DownloadClusterResourceBundle(w http.ResponseWriter, r *http.Request, bundleId BundleId)
// (POST /bundles/{bundleId}/status)
ReportClusterResourceBundleStatus(w http.ResponseWriter, r *http.Request, bundleId BundleId)
// (POST /exchangeToken)
ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request)
}
// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint.
type Unimplemented struct{}
// (GET /bootstrap)
func (_ Unimplemented) GetClusterBootstrapConfig(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// (GET /bundles)
func (_ Unimplemented) GetClusterResourceBundles(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// (GET /bundles/{bundleId}/download)
func (_ Unimplemented) DownloadClusterResourceBundle(w http.ResponseWriter, r *http.Request, bundleId BundleId) {
w.WriteHeader(http.StatusNotImplemented)
}
// (POST /bundles/{bundleId}/status)
func (_ Unimplemented) ReportClusterResourceBundleStatus(w http.ResponseWriter, r *http.Request, bundleId BundleId) {
w.WriteHeader(http.StatusNotImplemented)
}
// (POST /exchangeToken)
func (_ Unimplemented) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
// GetClusterBootstrapConfig operation middleware
func (siw *ServerInterfaceWrapper) GetClusterBootstrapConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetClusterBootstrapConfig(w, r)
}))
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
handler.ServeHTTP(w, r.WithContext(ctx))
}
// GetClusterResourceBundles operation middleware
func (siw *ServerInterfaceWrapper) GetClusterResourceBundles(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetClusterResourceBundles(w, r)
}))
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
handler.ServeHTTP(w, r.WithContext(ctx))
}
// DownloadClusterResourceBundle operation middleware
func (siw *ServerInterfaceWrapper) DownloadClusterResourceBundle(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
// ------------- Path parameter "bundleId" -------------
var bundleId BundleId
err = runtime.BindStyledParameterWithLocation("simple", false, "bundleId", runtime.ParamLocationPath, chi.URLParam(r, "bundleId"), &bundleId)
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "bundleId", Err: err})
return
}
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.DownloadClusterResourceBundle(w, r, bundleId)
}))
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ReportClusterResourceBundleStatus operation middleware
func (siw *ServerInterfaceWrapper) ReportClusterResourceBundleStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
// ------------- Path parameter "bundleId" -------------
var bundleId BundleId
err = runtime.BindStyledParameterWithLocation("simple", false, "bundleId", runtime.ParamLocationPath, chi.URLParam(r, "bundleId"), &bundleId)
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "bundleId", Err: err})
return
}
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.ReportClusterResourceBundleStatus(w, r, bundleId)
}))
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
handler.ServeHTTP(w, r.WithContext(ctx))
}
// ExchangeClusterIdentityToken operation middleware
func (siw *ServerInterfaceWrapper) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.ExchangeClusterIdentityToken(w, r)
}))
for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
handler.ServeHTTP(w, r.WithContext(ctx))
}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) http.Handler {
return HandlerWithOptions(si, ChiServerOptions{})
}
type ChiServerOptions struct {
BaseURL string
BaseRouter chi.Router
Middlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
return HandlerWithOptions(si, ChiServerOptions{
BaseRouter: r,
})
}
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
return HandlerWithOptions(si, ChiServerOptions{
BaseURL: baseURL,
BaseRouter: r,
})
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
r := options.BaseRouter
if r == nil {
r = chi.NewRouter()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
r.Group(func(r chi.Router) {
r.Get(options.BaseURL+"/bootstrap", wrapper.GetClusterBootstrapConfig)
})
r.Group(func(r chi.Router) {
r.Get(options.BaseURL+"/bundles", wrapper.GetClusterResourceBundles)
})
r.Group(func(r chi.Router) {
r.Get(options.BaseURL+"/bundles/{bundleId}/download", wrapper.DownloadClusterResourceBundle)
})
r.Group(func(r chi.Router) {
r.Post(options.BaseURL+"/bundles/{bundleId}/status", wrapper.ReportClusterResourceBundleStatus)
})
r.Group(func(r chi.Router) {
r.Post(options.BaseURL+"/exchangeToken", wrapper.ExchangeClusterIdentityToken)
})
return r
}
type GetClusterBootstrapConfigRequestObject struct {
}
type GetClusterBootstrapConfigResponseObject interface {
VisitGetClusterBootstrapConfigResponse(w http.ResponseWriter) error
}
type GetClusterBootstrapConfig200JSONResponse GetBootstrapConfigResponse
func (response GetClusterBootstrapConfig200JSONResponse) VisitGetClusterBootstrapConfigResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type GetClusterBootstrapConfig400JSONResponse ErrorResponse
func (response GetClusterBootstrapConfig400JSONResponse) VisitGetClusterBootstrapConfigResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type GetClusterBootstrapConfig500JSONResponse ErrorResponse
func (response GetClusterBootstrapConfig500JSONResponse) VisitGetClusterBootstrapConfigResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
return json.NewEncoder(w).Encode(response)
}
type GetClusterResourceBundlesRequestObject struct {
}
type GetClusterResourceBundlesResponseObject interface {
VisitGetClusterResourceBundlesResponse(w http.ResponseWriter) error
}
type GetClusterResourceBundles200JSONResponse GetBundlesResponse
func (response GetClusterResourceBundles200JSONResponse) VisitGetClusterResourceBundlesResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type GetClusterResourceBundles400JSONResponse ErrorResponse
func (response GetClusterResourceBundles400JSONResponse) VisitGetClusterResourceBundlesResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type GetClusterResourceBundles500JSONResponse ErrorResponse
func (response GetClusterResourceBundles500JSONResponse) VisitGetClusterResourceBundlesResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
return json.NewEncoder(w).Encode(response)
}
type DownloadClusterResourceBundleRequestObject struct {
BundleId BundleId `json:"bundleId"`
}
type DownloadClusterResourceBundleResponseObject interface {
VisitDownloadClusterResourceBundleResponse(w http.ResponseWriter) error
}
type DownloadClusterResourceBundle200JSONResponse DownloadBundleResponse
func (response DownloadClusterResourceBundle200JSONResponse) VisitDownloadClusterResourceBundleResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type DownloadClusterResourceBundle400JSONResponse ErrorResponse
func (response DownloadClusterResourceBundle400JSONResponse) VisitDownloadClusterResourceBundleResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type DownloadClusterResourceBundle404JSONResponse ErrorResponse
func (response DownloadClusterResourceBundle404JSONResponse) VisitDownloadClusterResourceBundleResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(404)
return json.NewEncoder(w).Encode(response)
}
type DownloadClusterResourceBundle500JSONResponse ErrorResponse
func (response DownloadClusterResourceBundle500JSONResponse) VisitDownloadClusterResourceBundleResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
return json.NewEncoder(w).Encode(response)
}
type ReportClusterResourceBundleStatusRequestObject struct {
BundleId BundleId `json:"bundleId"`
Body *ReportClusterResourceBundleStatusJSONRequestBody
}
type ReportClusterResourceBundleStatusResponseObject interface {
VisitReportClusterResourceBundleStatusResponse(w http.ResponseWriter) error
}
type ReportClusterResourceBundleStatus204Response struct {
}
func (response ReportClusterResourceBundleStatus204Response) VisitReportClusterResourceBundleStatusResponse(w http.ResponseWriter) error {
w.WriteHeader(204)
return nil
}
type ReportClusterResourceBundleStatus400JSONResponse ErrorResponse
func (response ReportClusterResourceBundleStatus400JSONResponse) VisitReportClusterResourceBundleStatusResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type ReportClusterResourceBundleStatus500JSONResponse ErrorResponse
func (response ReportClusterResourceBundleStatus500JSONResponse) VisitReportClusterResourceBundleStatusResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
return json.NewEncoder(w).Encode(response)
}
type ExchangeClusterIdentityTokenRequestObject struct {
Body *ExchangeClusterIdentityTokenJSONRequestBody
}
type ExchangeClusterIdentityTokenResponseObject interface {
VisitExchangeClusterIdentityTokenResponse(w http.ResponseWriter) error
}
type ExchangeClusterIdentityToken200JSONResponse ExchangeTokenResponse
func (response ExchangeClusterIdentityToken200JSONResponse) VisitExchangeClusterIdentityTokenResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type ExchangeClusterIdentityToken400JSONResponse ErrorResponse
func (response ExchangeClusterIdentityToken400JSONResponse) VisitExchangeClusterIdentityTokenResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type ExchangeClusterIdentityToken500JSONResponse ErrorResponse
func (response ExchangeClusterIdentityToken500JSONResponse) VisitExchangeClusterIdentityTokenResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
return json.NewEncoder(w).Encode(response)
}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
// (GET /bootstrap)
GetClusterBootstrapConfig(ctx context.Context, request GetClusterBootstrapConfigRequestObject) (GetClusterBootstrapConfigResponseObject, error)
// (GET /bundles)
GetClusterResourceBundles(ctx context.Context, request GetClusterResourceBundlesRequestObject) (GetClusterResourceBundlesResponseObject, error)
// (GET /bundles/{bundleId}/download)
DownloadClusterResourceBundle(ctx context.Context, request DownloadClusterResourceBundleRequestObject) (DownloadClusterResourceBundleResponseObject, error)
// (POST /bundles/{bundleId}/status)
ReportClusterResourceBundleStatus(ctx context.Context, request ReportClusterResourceBundleStatusRequestObject) (ReportClusterResourceBundleStatusResponseObject, error)
// (POST /exchangeToken)
ExchangeClusterIdentityToken(ctx context.Context, request ExchangeClusterIdentityTokenRequestObject) (ExchangeClusterIdentityTokenResponseObject, error)
}
type StrictHandlerFunc = strictnethttp.StrictHttpHandlerFunc
type StrictMiddlewareFunc = strictnethttp.StrictHttpMiddlewareFunc
type StrictHTTPServerOptions struct {
RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{
RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
},
ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusInternalServerError)
},
}}
}
func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
options StrictHTTPServerOptions
}
// GetClusterBootstrapConfig operation middleware
func (sh *strictHandler) GetClusterBootstrapConfig(w http.ResponseWriter, r *http.Request) {
var request GetClusterBootstrapConfigRequestObject
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.GetClusterBootstrapConfig(ctx, request.(GetClusterBootstrapConfigRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "GetClusterBootstrapConfig")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(GetClusterBootstrapConfigResponseObject); ok {
if err := validResponse.VisitGetClusterBootstrapConfigResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
// GetClusterResourceBundles operation middleware
func (sh *strictHandler) GetClusterResourceBundles(w http.ResponseWriter, r *http.Request) {
var request GetClusterResourceBundlesRequestObject
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.GetClusterResourceBundles(ctx, request.(GetClusterResourceBundlesRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "GetClusterResourceBundles")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(GetClusterResourceBundlesResponseObject); ok {
if err := validResponse.VisitGetClusterResourceBundlesResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
// DownloadClusterResourceBundle operation middleware
func (sh *strictHandler) DownloadClusterResourceBundle(w http.ResponseWriter, r *http.Request, bundleId BundleId) {
var request DownloadClusterResourceBundleRequestObject
request.BundleId = bundleId
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.DownloadClusterResourceBundle(ctx, request.(DownloadClusterResourceBundleRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "DownloadClusterResourceBundle")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(DownloadClusterResourceBundleResponseObject); ok {
if err := validResponse.VisitDownloadClusterResourceBundleResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
// ReportClusterResourceBundleStatus operation middleware
func (sh *strictHandler) ReportClusterResourceBundleStatus(w http.ResponseWriter, r *http.Request, bundleId BundleId) {
var request ReportClusterResourceBundleStatusRequestObject
request.BundleId = bundleId
var body ReportClusterResourceBundleStatusJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
return
}
request.Body = &body
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.ReportClusterResourceBundleStatus(ctx, request.(ReportClusterResourceBundleStatusRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "ReportClusterResourceBundleStatus")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(ReportClusterResourceBundleStatusResponseObject); ok {
if err := validResponse.VisitReportClusterResourceBundleStatusResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
// ExchangeClusterIdentityToken operation middleware
func (sh *strictHandler) ExchangeClusterIdentityToken(w http.ResponseWriter, r *http.Request) {
var request ExchangeClusterIdentityTokenRequestObject
var body ExchangeClusterIdentityTokenJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
return
}
request.Body = &body
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.ExchangeClusterIdentityToken(ctx, request.(ExchangeClusterIdentityTokenRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "ExchangeClusterIdentityToken")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(ExchangeClusterIdentityTokenResponseObject); ok {
if err := validResponse.VisitExchangeClusterIdentityTokenResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}

View file

@ -0,0 +1,8 @@
package: cluster
generate:
chi-server: true
strict-server: true
models: false
compatibility:
apply-chi-middleware-first-to-last: true
output: server.gen.go

View file

@ -0,0 +1,40 @@
package cluster
import (
"context"
"fmt"
"strconv"
"time"
"github.com/pomerium/pomerium/pkg/zero/apierror"
"github.com/pomerium/pomerium/pkg/zero/token"
)
// NewTokenFetcher creates a new authorization token fetcher
func NewTokenFetcher(endpoint string, opts ...ClientOption) (token.Fetcher, error) {
client, err := NewClientWithResponses(endpoint, opts...)
if err != nil {
return nil, fmt.Errorf("error creating client: %w", err)
}
return func(ctx context.Context, refreshToken string) (*token.Token, error) {
now := time.Now()
resp, err := apierror.CheckResponse[ExchangeTokenResponse](client.ExchangeClusterIdentityTokenWithResponse(ctx, ExchangeTokenRequest{
RefreshToken: refreshToken,
}))
if err != nil {
return nil, fmt.Errorf("error exchanging token: %w", err)
}
expiresSeconds, err := strconv.ParseInt(resp.ExpiresInSeconds, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing expires in: %w", err)
}
return &token.Token{
Bearer: resp.IdToken,
Expires: now.Add(time.Duration(expiresSeconds) * time.Second),
}, nil
}, nil
}

View file

@ -0,0 +1,55 @@
package cluster
import (
"net/url"
"sync"
"time"
)
// URLCache is a cache of URLs to download bundles from.
type URLCache struct {
mx sync.RWMutex
cache map[string]DownloadCacheEntry
}
// DownloadCacheEntry is a cache entry for a URL to download a bundle from.
type DownloadCacheEntry struct {
// URL is the URL to download the bundle from.
URL url.URL
// ExpiresAt is the time at which the URL expires.
ExpiresAt time.Time
// CaptureHeaders is a list of headers to capture from the response.
CaptureHeaders []string
}
// NewURLCache creates a new URL cache.
func NewURLCache() *URLCache {
return &URLCache{
cache: make(map[string]DownloadCacheEntry),
}
}
// Get gets the cache entry for the given key, if it exists and has not expired.
func (c *URLCache) Get(key string, minTTL time.Duration) (*DownloadCacheEntry, bool) {
c.mx.RLock()
defer c.mx.RUnlock()
entry, ok := c.cache[key]
if !ok {
return nil, false
}
if time.Until(entry.ExpiresAt) < minTTL {
return nil, false
}
return &entry, true
}
// Set sets the cache entry for the given key.
func (c *URLCache) Set(key string, entry DownloadCacheEntry) {
c.mx.Lock()
defer c.mx.Unlock()
c.cache[key] = entry
}

87
pkg/zero/config.go Normal file
View file

@ -0,0 +1,87 @@
package zero
import (
"fmt"
"net/http"
"time"
)
// Option is a functional option for the SDK
type Option func(*config)
type config struct {
clusterAPIEndpoint string
connectAPIEndpoint string
apiToken string
httpClient *http.Client
downloadURLCacheTTL time.Duration
}
// WithClusterAPIEndpoint sets the cluster API endpoint
func WithClusterAPIEndpoint(endpoint string) Option {
return func(cfg *config) {
cfg.clusterAPIEndpoint = endpoint
}
}
// WithConnectAPIEndpoint sets the connect API endpoint
func WithConnectAPIEndpoint(endpoint string) Option {
return func(cfg *config) {
cfg.connectAPIEndpoint = endpoint
}
}
// WithAPIToken sets the API token
func WithAPIToken(token string) Option {
return func(cfg *config) {
cfg.apiToken = token
}
}
// WithHTTPClient sets the HTTP client
func WithHTTPClient(client *http.Client) Option {
return func(cfg *config) {
cfg.httpClient = client
}
}
// WithDownloadURLCacheTTL sets the minimum TTL for download URL cache entries
func WithDownloadURLCacheTTL(ttl time.Duration) Option {
return func(cfg *config) {
cfg.downloadURLCacheTTL = ttl
}
}
func newConfig(opts ...Option) (*config, error) {
cfg := new(config)
for _, opt := range []Option{
WithHTTPClient(http.DefaultClient),
WithDownloadURLCacheTTL(15 * time.Minute),
} {
opt(cfg)
}
for _, opt := range opts {
opt(cfg)
}
if err := cfg.validate(); err != nil {
return nil, err
}
return cfg, nil
}
func (c *config) validate() error {
if c.clusterAPIEndpoint == "" {
return fmt.Errorf("cluster API endpoint is required")
}
if c.connectAPIEndpoint == "" {
return fmt.Errorf("connect API endpoint is required")
}
if c.apiToken == "" {
return fmt.Errorf("API token is required")
}
if c.httpClient == nil {
return fmt.Errorf("HTTP client is required")
}
return nil
}

View file

@ -0,0 +1,57 @@
package mux
import "context"
type config struct {
onConnected func(ctx context.Context)
onDisconnected func(ctx context.Context)
onBundleUpdated func(ctx context.Context, key string)
onBootstrapConfigUpdated func(ctx context.Context)
}
// WatchOption allows to specify callbacks for various events
type WatchOption func(*config)
// WithOnConnected sets the callback for when the connection is established
func WithOnConnected(onConnected func(context.Context)) WatchOption {
return func(cfg *config) {
cfg.onConnected = onConnected
}
}
// WithOnDisconnected sets the callback for when the connection is lost
func WithOnDisconnected(onDisconnected func(context.Context)) WatchOption {
return func(cfg *config) {
cfg.onDisconnected = onDisconnected
}
}
// WithOnBundleUpdated sets the callback for when the bundle is updated
func WithOnBundleUpdated(onBundleUpdated func(ctx context.Context, key string)) WatchOption {
return func(cfg *config) {
cfg.onBundleUpdated = onBundleUpdated
}
}
// WithOnBootstrapConfigUpdated sets the callback for when the bootstrap config is updated
func WithOnBootstrapConfigUpdated(onBootstrapConfigUpdated func(context.Context)) WatchOption {
return func(cfg *config) {
cfg.onBootstrapConfigUpdated = onBootstrapConfigUpdated
}
}
func newConfig(opts ...WatchOption) *config {
cfg := &config{}
for _, opt := range []WatchOption{
WithOnConnected(func(_ context.Context) {}),
WithOnDisconnected(func(_ context.Context) {}),
WithOnBundleUpdated(func(_ context.Context, key string) {}),
WithOnBootstrapConfigUpdated(func(_ context.Context) {}),
} {
opt(cfg)
}
for _, opt := range opts {
opt(cfg)
}
return cfg
}

View file

@ -0,0 +1,109 @@
package mux
import (
"context"
"fmt"
"github.com/pomerium/pomerium/pkg/zero/apierror"
"github.com/pomerium/pomerium/pkg/zero/connect"
)
// Watch watches for changes to the config until either context is canceled,
// or an error occurs while muxing
func (svc *Mux) Watch(ctx context.Context, opts ...WatchOption) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-svc.ready:
}
cfg := newConfig(opts...)
connected := svc.connected.Load()
if connected {
cfg.onConnected(ctx)
} else {
cfg.onDisconnected(ctx)
}
return svc.mux.Receive(ctx, func(ctx context.Context, msg message) error {
return dispatch(ctx, cfg, msg)
})
}
func dispatch(ctx context.Context, cfg *config, msg message) error {
switch {
case msg.stateChange != nil:
switch *msg.stateChange {
case connected:
cfg.onConnected(ctx)
case disconnected:
cfg.onDisconnected(ctx)
default:
return fmt.Errorf("unknown state change")
}
case msg.Message != nil:
switch msg.Message.Message.(type) {
case *connect.Message_ConfigUpdated:
cfg.onBundleUpdated(ctx, "config")
case *connect.Message_BootstrapConfigUpdated:
cfg.onBootstrapConfigUpdated(ctx)
default:
return fmt.Errorf("unknown message type")
}
default:
return fmt.Errorf("unknown message payload")
}
return nil
}
type message struct {
*stateChange
*connect.Message
}
type stateChange string
const (
connected stateChange = "connected"
disconnected stateChange = "disconnected"
)
// Publish publishes a message to the fanout
// we treat errors returned from the fanout as terminal,
// as they are generally non recoverable
func (svc *Mux) publish(ctx context.Context, msg message) error {
err := svc.mux.Publish(ctx, msg)
if err != nil {
return apierror.NewTerminalError(err)
}
return nil
}
func (svc *Mux) onConnected(ctx context.Context) error {
s := connected
svc.connected.Store(true)
err := svc.publish(ctx, message{stateChange: &s})
if err != nil {
return fmt.Errorf("onConnected: %w", err)
}
return nil
}
func (svc *Mux) onDisconnected(ctx context.Context) error {
s := disconnected
svc.connected.Store(false)
err := svc.publish(ctx, message{stateChange: &s})
if err != nil {
return fmt.Errorf("onDisconnected: %w", err)
}
return nil
}
func (svc *Mux) onMessage(ctx context.Context, msg *connect.Message) error {
err := svc.publish(ctx, message{Message: msg})
if err != nil {
return fmt.Errorf("onMessage: %w", err)
}
return nil
}

View file

@ -0,0 +1,108 @@
// Package mux provides the way to listen for updates from the cloud
package mux
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/go-multierror"
"github.com/rs/zerolog/log"
"github.com/pomerium/pomerium/pkg/fanout"
"github.com/pomerium/pomerium/pkg/zero/apierror"
"github.com/pomerium/pomerium/pkg/zero/connect"
)
// Mux is the service that listens for updates from the cloud
type Mux struct {
client connect.ConnectClient
mux *fanout.FanOut[message]
ready chan struct{}
connected atomic.Bool
}
// New creates a new mux service that listens for updates from the cloud
func New(client connect.ConnectClient) *Mux {
svc := &Mux{
client: client,
ready: make(chan struct{}),
}
return svc
}
// Run starts the service
func (svc *Mux) Run(ctx context.Context, opts ...fanout.Option) error {
ctx, cancel := context.WithCancelCause(ctx)
defer func() { cancel(ctx.Err()) }()
svc.mux = fanout.Start[message](ctx, opts...)
close(svc.ready)
err := svc.run(ctx)
if err != nil {
cancel(err)
return err
}
return nil
}
func (svc *Mux) run(ctx context.Context) error {
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 0
ticker := time.NewTicker(time.Microsecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
err := svc.subscribeAndDispatch(ctx, bo.Reset)
if err != nil {
ticker.Reset(bo.NextBackOff())
}
if apierror.IsTerminalError(err) {
return err
}
}
}
func (svc *Mux) subscribeAndDispatch(ctx context.Context, onConnected func()) (err error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
stream, err := svc.client.Subscribe(ctx, &connect.SubscribeRequest{})
if err != nil {
return fmt.Errorf("subscribe: %w", err)
}
onConnected()
if err = svc.onConnected(ctx); err != nil {
return err
}
defer func() {
err = multierror.Append(err, svc.onDisconnected(ctx)).ErrorOrNil()
}()
log.Ctx(ctx).Info().Msg("subscribed to connect service")
for {
msg, err := stream.Recv()
log.Ctx(ctx).Info().Interface("msg", msg).Err(err).Msg("receive")
if err != nil {
return fmt.Errorf("receive: %w", err)
}
err = svc.onMessage(ctx, msg)
if err != nil {
return err
}
}
}

View file

@ -0,0 +1,10 @@
version: v1
plugins:
- name: go
out: .
opt: paths=source_relative
- name: go-grpc
out: .
opt:
- paths=source_relative
- require_unimplemented_servers=false

View file

@ -0,0 +1,8 @@
version: v1
build: {}
lint:
use:
- DEFAULT
breaking:
use:
- FILE

View file

@ -0,0 +1,83 @@
package connect
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
grpc_backoff "google.golang.org/grpc/backoff"
)
const (
defaultDialTimeout = time.Hour
)
type client struct {
config *Config
tokenProvider TokenProviderFn
minTokenTTL time.Duration
}
// TokenProviderFn is a function that returns a token that is expected to be valid for at least minTTL
type TokenProviderFn func(ctx context.Context, minTTL time.Duration) (string, error)
// NewAuthorizedConnectClient creates a new gRPC client for the connect service
func NewAuthorizedConnectClient(
ctx context.Context,
endpoint string,
tokenProvider TokenProviderFn,
) (ConnectClient, error) {
cfg, err := NewConfig(endpoint)
if err != nil {
return nil, err
}
cc := &client{
tokenProvider: tokenProvider,
config: cfg,
// streaming connection would reset based on token duration,
// so we need it be close to max duration 1hr
minTokenTTL: time.Minute * 55,
}
grpcConn, err := cc.getGRPCConn(ctx)
if err != nil {
return nil, err
}
return NewConnectClient(grpcConn), nil
}
func (c *client) getGRPCConn(ctx context.Context) (*grpc.ClientConn, error) {
conn, err := grpc.DialContext(ctx,
c.config.GetConnectionURI(),
append(c.config.GetDialOptions(),
grpc.WithPerRPCCredentials(c),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: grpc_backoff.DefaultConfig,
// the MinConnectTimeout is confusing and is actually the max timeout as per grpc implementation
MinConnectTimeout: c.config.GetDialTimeout(),
}),
)...)
if err != nil {
return nil, fmt.Errorf("error dialing grpc server: %w", err)
}
return conn, nil
}
// GetRequestMetadata implements credentials.PerRPCCredentials
func (c *client) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
token, err := c.tokenProvider(ctx, c.minTokenTTL)
if err != nil {
return nil, err
}
return map[string]string{
"authorization": fmt.Sprintf("Bearer %s", token),
}, nil
}
// RequireTransportSecurity implements credentials.PerRPCCredentials
func (c *client) RequireTransportSecurity() bool {
return c.config.RequireTLS()
}

View file

@ -0,0 +1,96 @@
package connect_test
import (
"context"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
"github.com/pomerium/pomerium/pkg/zero/connect"
"github.com/pomerium/pomerium/pkg/zero/token"
)
func TestConfig(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
endpoint string
connectionURI string
requireTLS bool
expectError bool
}{
{"http://localhost:8721", "dns:localhost:8721", false, false},
{"https://localhost:8721", "dns:localhost:8721", true, false},
{"http://localhost:8721/", "dns:localhost:8721", false, false},
{"https://localhost:8721/", "dns:localhost:8721", true, false},
{"http://localhost", "dns:localhost:80", false, false},
{"https://localhost", "dns:localhost:443", true, false},
{endpoint: "", expectError: true},
{endpoint: "http://", expectError: true},
{endpoint: "https://", expectError: true},
{endpoint: "localhost:8721", expectError: true},
{endpoint: "http://localhost:8721/path", expectError: true},
{endpoint: "https://localhost:8721/path", expectError: true},
} {
tc := tc
t.Run(tc.endpoint, func(t *testing.T) {
t.Parallel()
cfg, err := connect.NewConfig(tc.endpoint)
if tc.expectError {
require.Error(t, err)
return
}
if assert.NoError(t, err) {
assert.Equal(t, tc.connectionURI, cfg.GetConnectionURI(), "connection uri")
assert.Equal(t, tc.requireTLS, cfg.RequireTLS(), "require tls")
}
})
}
}
func TestConnectClient(t *testing.T) {
refreshToken := os.Getenv("CONNECT_CLUSTER_IDENTITY_TOKEN")
if refreshToken == "" {
t.Skip("CONNECT_CLUSTER_IDENTITY_TOKEN not set")
}
connectServerEndpoint := os.Getenv("CONNECT_SERVER_ENDPOINT")
if connectServerEndpoint == "" {
connectServerEndpoint = "http://localhost:8721"
}
clusterAPIEndpoint := os.Getenv("CLUSTER_API_ENDPOINT")
if clusterAPIEndpoint == "" {
clusterAPIEndpoint = "http://localhost:8720/cluster/v1"
}
fetcher, err := cluster_api.NewTokenFetcher(clusterAPIEndpoint)
require.NoError(t, err, "error creating token fetcher")
ctx := context.Background()
deadline, ok := t.Deadline()
if ok {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, deadline.Add(-1*time.Second))
t.Cleanup(cancel)
}
tokenCache := token.NewCache(fetcher, refreshToken)
connectClient, err := connect.NewAuthorizedConnectClient(ctx, connectServerEndpoint, tokenCache.GetToken)
require.NoError(t, err, "error creating connect client")
stream, err := connectClient.Subscribe(ctx, &connect.SubscribeRequest{})
require.NoError(t, err, "error subscribing")
for {
msg, err := stream.Recv()
require.NoError(t, err, "error receiving message")
t.Log(msg)
}
}

121
pkg/zero/connect/config.go Normal file
View file

@ -0,0 +1,121 @@
package connect
import (
"crypto/tls"
"fmt"
"net"
"net/url"
"regexp"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
// Config is the configuration for the gRPC client
type Config struct {
connectionURI string
// requireTLS is whether TLS should be used or cleartext
requireTLS bool
// opts are additional options to pass to the gRPC client
opts []grpc.DialOption
}
// NewConfig returns a new Config from an endpoint string, that has to be in a URL format.
// The endpoint can be either http:// or https:// that will be used to determine whether TLS should be used.
// if port is not specified, it will be inferred from the scheme (80 for http, 443 for https).
func NewConfig(endpoint string) (*Config, error) {
c := new(Config)
err := c.parseEndpoint(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint: %w", err)
}
c.buildTLSOptions()
return c, nil
}
// GetConnectionURI returns connection string conforming to https://github.com/grpc/grpc/blob/master/doc/naming.md
func (c *Config) GetConnectionURI() string {
return c.connectionURI
}
// GetDialTimeout returns the timeout for the dial operation
func (c *Config) GetDialTimeout() time.Duration {
return defaultDialTimeout
}
// RequireTLS returns whether TLS should be used or cleartext
func (c *Config) RequireTLS() bool {
return c.requireTLS
}
// GetDialOptions returns the dial options to pass to the gRPC client
func (c *Config) GetDialOptions() []grpc.DialOption {
return c.opts
}
func (c *Config) buildTLSOptions() {
creds := insecure.NewCredentials()
if c.requireTLS {
creds = credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
})
}
c.opts = append(c.opts, grpc.WithTransportCredentials(creds))
}
func (c *Config) parseEndpoint(endpoint string) error {
u, err := url.Parse(endpoint)
if err != nil {
return fmt.Errorf("error parsing endpoint url: %w", err)
}
if u.Path != "" && u.Path != "/" {
return fmt.Errorf("endpoint path is not supported: %s", u.Path)
}
host, port, err := splitHostPort(u.Host)
if err != nil {
return fmt.Errorf("error splitting host and port: %w", err)
}
var requireTLS bool
if u.Scheme == "http" {
requireTLS = false
if port == "" {
port = "80"
}
} else if u.Scheme == "https" {
requireTLS = true
if port == "" {
port = "443"
}
} else {
return fmt.Errorf("unsupported url scheme: %s", u.Scheme)
}
c.connectionURI = fmt.Sprintf("dns:%s:%s", host, port)
c.requireTLS = requireTLS
return nil
}
var rePort = regexp.MustCompile(`:(\d+)$`)
func splitHostPort(hostport string) (host, port string, err error) {
if hostport == "" {
return "", "", fmt.Errorf("empty hostport")
}
if rePort.MatchString(hostport) {
host, port, err = net.SplitHostPort(hostport)
if host == "" {
return "", "", fmt.Errorf("empty host")
}
if port == "" {
return "", "", fmt.Errorf("empty port")
}
return host, port, err
}
return hostport, "", nil
}

View file

@ -0,0 +1,384 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc (unknown)
// source: connect.proto
package connect
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// SubscribeRequest is used to subscribe to a stream of messages
// from the Zero Cloud to the Pomerium Core.
//
// The Authorization: Bearer header must contain a valid token,
// that belongs to a cluster identity with appropriate claims set.
type SubscribeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribeRequest) Reset() {
*x = SubscribeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_connect_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeRequest) ProtoMessage() {}
func (x *SubscribeRequest) ProtoReflect() protoreflect.Message {
mi := &file_connect_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeRequest.ProtoReflect.Descriptor instead.
func (*SubscribeRequest) Descriptor() ([]byte, []int) {
return file_connect_proto_rawDescGZIP(), []int{0}
}
// Message is an aggregate of all possible messages that can be sent
// from the cloud to the core in managed mode.
type Message struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Message:
//
// *Message_ConfigUpdated
// *Message_BootstrapConfigUpdated
Message isMessage_Message `protobuf_oneof:"message"`
}
func (x *Message) Reset() {
*x = Message{}
if protoimpl.UnsafeEnabled {
mi := &file_connect_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Message) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_connect_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
func (*Message) Descriptor() ([]byte, []int) {
return file_connect_proto_rawDescGZIP(), []int{1}
}
func (m *Message) GetMessage() isMessage_Message {
if m != nil {
return m.Message
}
return nil
}
func (x *Message) GetConfigUpdated() *ConfigUpdated {
if x, ok := x.GetMessage().(*Message_ConfigUpdated); ok {
return x.ConfigUpdated
}
return nil
}
func (x *Message) GetBootstrapConfigUpdated() *BootstrapConfigUpdated {
if x, ok := x.GetMessage().(*Message_BootstrapConfigUpdated); ok {
return x.BootstrapConfigUpdated
}
return nil
}
type isMessage_Message interface {
isMessage_Message()
}
type Message_ConfigUpdated struct {
ConfigUpdated *ConfigUpdated `protobuf:"bytes,1,opt,name=config_updated,json=configUpdated,proto3,oneof"`
}
type Message_BootstrapConfigUpdated struct {
BootstrapConfigUpdated *BootstrapConfigUpdated `protobuf:"bytes,2,opt,name=bootstrap_config_updated,json=bootstrapConfigUpdated,proto3,oneof"`
}
func (*Message_ConfigUpdated) isMessage_Message() {}
func (*Message_BootstrapConfigUpdated) isMessage_Message() {}
// ConfigUpdated is sent when the configuration has been updated
// for the connected Pomerium Core deployment
type ConfigUpdated struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// version of the configuration changeset
ChangesetVersion int64 `protobuf:"varint,1,opt,name=changeset_version,json=changesetVersion,proto3" json:"changeset_version,omitempty"`
}
func (x *ConfigUpdated) Reset() {
*x = ConfigUpdated{}
if protoimpl.UnsafeEnabled {
mi := &file_connect_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfigUpdated) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfigUpdated) ProtoMessage() {}
func (x *ConfigUpdated) ProtoReflect() protoreflect.Message {
mi := &file_connect_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConfigUpdated.ProtoReflect.Descriptor instead.
func (*ConfigUpdated) Descriptor() ([]byte, []int) {
return file_connect_proto_rawDescGZIP(), []int{2}
}
func (x *ConfigUpdated) GetChangesetVersion() int64 {
if x != nil {
return x.ChangesetVersion
}
return 0
}
// BootstrapConfigUpdated is sent when the bootstrap configuration has been
// updated. Bootstrap configuration is received via cluster API directly, and
// does not involve long running operations to construct it, like with a regular
// config.
type BootstrapConfigUpdated struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *BootstrapConfigUpdated) Reset() {
*x = BootstrapConfigUpdated{}
if protoimpl.UnsafeEnabled {
mi := &file_connect_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BootstrapConfigUpdated) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BootstrapConfigUpdated) ProtoMessage() {}
func (x *BootstrapConfigUpdated) ProtoReflect() protoreflect.Message {
mi := &file_connect_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BootstrapConfigUpdated.ProtoReflect.Descriptor instead.
func (*BootstrapConfigUpdated) Descriptor() ([]byte, []int) {
return file_connect_proto_rawDescGZIP(), []int{3}
}
var File_connect_proto protoreflect.FileDescriptor
var file_connect_proto_rawDesc = []byte{
0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x0d, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x7a, 0x65, 0x72, 0x6f, 0x22, 0x12,
0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45,
0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75,
0x6d, 0x2e, 0x7a, 0x65, 0x72, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x61, 0x0a, 0x18, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72,
0x61, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69,
0x75, 0x6d, 0x2e, 0x7a, 0x65, 0x72, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61,
0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00,
0x52, 0x16, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x22, 0x3c, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x65,
0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x10, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x18, 0x0a, 0x16, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x32, 0x51, 0x0a, 0x07, 0x43,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x46, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x7a,
0x65, 0x72, 0x6f, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2e,
0x7a, 0x65, 0x72, 0x6f, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x42, 0x2f,
0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d,
0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70,
0x6b, 0x67, 0x2f, 0x7a, 0x65, 0x72, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_connect_proto_rawDescOnce sync.Once
file_connect_proto_rawDescData = file_connect_proto_rawDesc
)
func file_connect_proto_rawDescGZIP() []byte {
file_connect_proto_rawDescOnce.Do(func() {
file_connect_proto_rawDescData = protoimpl.X.CompressGZIP(file_connect_proto_rawDescData)
})
return file_connect_proto_rawDescData
}
var file_connect_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_connect_proto_goTypes = []interface{}{
(*SubscribeRequest)(nil), // 0: pomerium.zero.SubscribeRequest
(*Message)(nil), // 1: pomerium.zero.Message
(*ConfigUpdated)(nil), // 2: pomerium.zero.ConfigUpdated
(*BootstrapConfigUpdated)(nil), // 3: pomerium.zero.BootstrapConfigUpdated
}
var file_connect_proto_depIdxs = []int32{
2, // 0: pomerium.zero.Message.config_updated:type_name -> pomerium.zero.ConfigUpdated
3, // 1: pomerium.zero.Message.bootstrap_config_updated:type_name -> pomerium.zero.BootstrapConfigUpdated
0, // 2: pomerium.zero.Connect.Subscribe:input_type -> pomerium.zero.SubscribeRequest
1, // 3: pomerium.zero.Connect.Subscribe:output_type -> pomerium.zero.Message
3, // [3:4] is the sub-list for method output_type
2, // [2:3] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_connect_proto_init() }
func file_connect_proto_init() {
if File_connect_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_connect_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_connect_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Message); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_connect_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfigUpdated); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_connect_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BootstrapConfigUpdated); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_connect_proto_msgTypes[1].OneofWrappers = []interface{}{
(*Message_ConfigUpdated)(nil),
(*Message_BootstrapConfigUpdated)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_connect_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_connect_proto_goTypes,
DependencyIndexes: file_connect_proto_depIdxs,
MessageInfos: file_connect_proto_msgTypes,
}.Build()
File_connect_proto = out.File
file_connect_proto_rawDesc = nil
file_connect_proto_goTypes = nil
file_connect_proto_depIdxs = nil
}

View file

@ -0,0 +1,41 @@
syntax = "proto3";
package pomerium.zero;
option go_package = "github.com/pomerium/pomerium/pkg/zero/connect";
// SubscribeRequest is used to subscribe to a stream of messages
// from the Zero Cloud to the Pomerium Core.
//
// The Authorization: Bearer header must contain a valid token,
// that belongs to a cluster identity with appropriate claims set.
message SubscribeRequest {}
// Message is an aggregate of all possible messages that can be sent
// from the cloud to the core in managed mode.
message Message {
oneof message {
ConfigUpdated config_updated = 1;
BootstrapConfigUpdated bootstrap_config_updated = 2;
}
}
// ConfigUpdated is sent when the configuration has been updated
// for the connected Pomerium Core deployment
message ConfigUpdated {
// version of the configuration changeset
int64 changeset_version = 1;
}
// BootstrapConfigUpdated is sent when the bootstrap configuration has been
// updated. Bootstrap configuration is received via cluster API directly, and
// does not involve long running operations to construct it, like with a regular
// config.
message BootstrapConfigUpdated {}
// Connect service is used to maintain a persistent connection between the
// Pomerium Core and Zero Cloud and receive messages from the cloud.
service Connect {
// Subscribe is used to send a stream of messages from the Zero Cloud to the
// Pomerium Core in managed mode.
rpc Subscribe(SubscribeRequest) returns (stream Message);
}

View file

@ -0,0 +1,138 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: connect.proto
package connect
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
Connect_Subscribe_FullMethodName = "/pomerium.zero.Connect/Subscribe"
)
// ConnectClient is the client API for Connect service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ConnectClient interface {
// Subscribe is used to send a stream of messages from the Zero Cloud to the
// Pomerium Core in managed mode.
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Connect_SubscribeClient, error)
}
type connectClient struct {
cc grpc.ClientConnInterface
}
func NewConnectClient(cc grpc.ClientConnInterface) ConnectClient {
return &connectClient{cc}
}
func (c *connectClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Connect_SubscribeClient, error) {
stream, err := c.cc.NewStream(ctx, &Connect_ServiceDesc.Streams[0], Connect_Subscribe_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &connectSubscribeClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Connect_SubscribeClient interface {
Recv() (*Message, error)
grpc.ClientStream
}
type connectSubscribeClient struct {
grpc.ClientStream
}
func (x *connectSubscribeClient) Recv() (*Message, error) {
m := new(Message)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// ConnectServer is the server API for Connect service.
// All implementations should embed UnimplementedConnectServer
// for forward compatibility
type ConnectServer interface {
// Subscribe is used to send a stream of messages from the Zero Cloud to the
// Pomerium Core in managed mode.
Subscribe(*SubscribeRequest, Connect_SubscribeServer) error
}
// UnimplementedConnectServer should be embedded to have forward compatible implementations.
type UnimplementedConnectServer struct {
}
func (UnimplementedConnectServer) Subscribe(*SubscribeRequest, Connect_SubscribeServer) error {
return status.Errorf(codes.Unimplemented, "method Subscribe not implemented")
}
// UnsafeConnectServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ConnectServer will
// result in compilation errors.
type UnsafeConnectServer interface {
mustEmbedUnimplementedConnectServer()
}
func RegisterConnectServer(s grpc.ServiceRegistrar, srv ConnectServer) {
s.RegisterService(&Connect_ServiceDesc, srv)
}
func _Connect_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ConnectServer).Subscribe(m, &connectSubscribeServer{stream})
}
type Connect_SubscribeServer interface {
Send(*Message) error
grpc.ServerStream
}
type connectSubscribeServer struct {
grpc.ServerStream
}
func (x *connectSubscribeServer) Send(m *Message) error {
return x.ServerStream.SendMsg(m)
}
// Connect_ServiceDesc is the grpc.ServiceDesc for Connect service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Connect_ServiceDesc = grpc.ServiceDesc{
ServiceName: "pomerium.zero.Connect",
HandlerType: (*ConnectServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Subscribe",
Handler: _Connect_Subscribe_Handler,
ServerStreams: true,
},
},
Metadata: "connect.proto",
}

View file

@ -0,0 +1,4 @@
// Package connect provides the way to listen for updates from the cloud
package connect
//go:generate go run github.com/bufbuild/buf/cmd/buf@v1.28.1 generate --path connect.proto --config buf.yaml

251
pkg/zero/download.go Normal file
View file

@ -0,0 +1,251 @@
package zero
import (
"bytes"
"compress/gzip"
"context"
"encoding/xml"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"strconv"
"time"
"github.com/rs/zerolog/log"
"github.com/pomerium/pomerium/pkg/zero/apierror"
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
)
const (
maxErrorResponseBodySize = 2 << 14 // 32kb
maxUncompressedBlobSize = 2 << 30 // 1gb
)
// DownloadClusterResourceBundle downloads given cluster resource bundle to given writer.
func (api *API) DownloadClusterResourceBundle(
ctx context.Context,
dst io.Writer,
id string,
current *DownloadConditional,
) (*DownloadResult, error) {
req, err := api.getDownloadRequest(ctx, id, current)
if err != nil {
return nil, fmt.Errorf("get download request: %w", err)
}
resp, err := api.cfg.httpClient.Do(req.Request)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotModified {
return &DownloadResult{NotModified: true}, nil
}
if resp.StatusCode != http.StatusOK {
return nil, httpDownloadError(ctx, resp)
}
var r io.Reader = resp.Body
if resp.Header.Get("Content-Encoding") == "gzip" {
zr, err := gzip.NewReader(r)
if err != nil {
return nil, fmt.Errorf("gzip reader: %w", err)
}
defer zr.Close()
r = io.LimitReader(zr, maxUncompressedBlobSize)
}
_, err = io.Copy(dst, r)
if err != nil {
return nil, fmt.Errorf("write body: %w", err)
}
updated, err := newConditionalFromResponse(resp)
if err != nil {
return nil, fmt.Errorf("cannot obtain cache conditions from response: %w", err)
}
return &DownloadResult{
DownloadConditional: updated,
Metadata: extractMetadata(resp.Header, req.CaptureHeaders),
}, nil
}
type downloadRequest struct {
*http.Request
cluster_api.DownloadCacheEntry
}
func (api *API) getDownloadRequest(ctx context.Context, id string, current *DownloadConditional) (*downloadRequest, error) {
params, err := api.getDownloadParams(ctx, id)
if err != nil {
return nil, fmt.Errorf("get download URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, params.URL.String(), nil)
if err != nil {
return nil, fmt.Errorf("new request: %w", err)
}
req.Header.Set("Accept-Encoding", "gzip")
err = current.SetHeaders(req)
if err != nil {
return nil, fmt.Errorf("set conditional download headers: %w", err)
}
return &downloadRequest{
Request: req,
DownloadCacheEntry: *params,
}, nil
}
func (api *API) getDownloadParams(ctx context.Context, id string) (*cluster_api.DownloadCacheEntry, error) {
param, ok := api.downloadURLCache.Get(id, api.cfg.downloadURLCacheTTL)
if ok {
return param, nil
}
return api.updateBundleDownloadParams(ctx, id)
}
func (api *API) updateBundleDownloadParams(ctx context.Context, id string) (*cluster_api.DownloadCacheEntry, error) {
now := time.Now()
resp, err := apierror.CheckResponse[cluster_api.DownloadBundleResponse](
api.cluster.DownloadClusterResourceBundleWithResponse(ctx, id),
)
if err != nil {
return nil, fmt.Errorf("get bundle download URL: %w", err)
}
expiresSeconds, err := strconv.ParseInt(resp.ExpiresInSeconds, 10, 64)
if err != nil {
return nil, fmt.Errorf("parse expiration: %w", err)
}
u, err := url.Parse(resp.Url)
if err != nil {
return nil, fmt.Errorf("parse url: %w", err)
}
param := cluster_api.DownloadCacheEntry{
URL: *u,
ExpiresAt: now.Add(time.Duration(expiresSeconds) * time.Second),
CaptureHeaders: resp.CaptureMetadataHeaders,
}
api.downloadURLCache.Set(id, param)
return &param, nil
}
// DownloadResult contains the result of a download operation
type DownloadResult struct {
// NotModified is true if the bundle has not been modified
NotModified bool
// DownloadConditional contains the new conditional
*DownloadConditional
// Metadata contains the metadata of the downloaded bundle
Metadata map[string]string
}
// DownloadConditional contains the conditional headers for a download operation
type DownloadConditional struct {
ETag string
LastModified string
}
// Validate validates the conditional headers
func (c *DownloadConditional) Validate() error {
if c.ETag == "" && c.LastModified == "" {
return fmt.Errorf("either ETag or LastModified must be set")
}
return nil
}
// SetHeaders sets the conditional headers on the given request
func (c *DownloadConditional) SetHeaders(req *http.Request) error {
if c == nil {
return nil
}
if err := c.Validate(); err != nil {
return err
}
req.Header.Set("If-None-Match", c.ETag)
req.Header.Set("If-Modified-Since", c.LastModified)
return nil
}
func newConditionalFromResponse(resp *http.Response) (*DownloadConditional, error) {
c := &DownloadConditional{
ETag: resp.Header.Get("ETag"),
LastModified: resp.Header.Get("Last-Modified"),
}
if err := c.Validate(); err != nil {
return nil, err
}
return c, nil
}
type xmlError struct {
XMLName xml.Name `xml:"Error"`
Code string `xml:"Code"`
Message string `xml:"Message"`
Details string `xml:"Details"`
}
func (e xmlError) Error() string {
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
func tryXMLError(body []byte) (bool, error) {
var xmlErr xmlError
err := xml.Unmarshal(body, &xmlErr)
if err != nil {
return false, fmt.Errorf("unmarshal xml error: %w", err)
}
return true, xmlErr
}
func httpDownloadError(ctx context.Context, resp *http.Response) error {
var buf bytes.Buffer
_, err := io.Copy(&buf, io.LimitReader(resp.Body, maxErrorResponseBodySize))
if isXML(resp.Header.Get("Content-Type")) {
ok, err := tryXMLError(buf.Bytes())
if ok {
return err
}
}
log.Ctx(ctx).Debug().Err(err).
Str("error", resp.Status).
Str("body", buf.String()).Msg("bundle download error")
return fmt.Errorf("download error: %s", resp.Status)
}
// isXML parses content-type for application/xml
func isXML(ct string) bool {
mediaType, _, err := mime.ParseMediaType(ct)
if err != nil {
return false
}
return mediaType == "application/xml"
}
func extractMetadata(header http.Header, keys []string) map[string]string {
m := make(map[string]string)
for _, k := range keys {
v := header.Get(k)
if v != "" {
m[k] = v
}
}
return m
}

100
pkg/zero/token/cache.go Normal file
View file

@ -0,0 +1,100 @@
// Package token provides a thread-safe cache of a authorization token that may be used across http and grpc clients
package token
import (
"context"
"fmt"
"sync/atomic"
"time"
)
const (
maxLockWait = 30 * time.Second
)
// Cache is a thread-safe cache of a authorization token
// that may be used across http and grpc clients
type Cache struct {
TimeNow func() time.Time
refreshToken string
fetcher Fetcher
lock chan struct{}
token atomic.Value
}
// Fetcher is a function that fetches a new token
type Fetcher func(ctx context.Context, refreshToken string) (*Token, error)
// Token is a bearer token
type Token struct {
// Bearer is the bearer token
Bearer string
// Expires is the time the token expires
Expires time.Time
}
// ExpiresAfter returns true if the token expires after the given time
func (t *Token) ExpiresAfter(tm time.Time) bool {
return t != nil && t.Expires.After(tm)
}
// NewCache creates a new token cache
func NewCache(fetcher Fetcher, refreshToken string) *Cache {
return &Cache{
lock: make(chan struct{}, 1),
fetcher: fetcher,
refreshToken: refreshToken,
}
}
func (c *Cache) timeNow() time.Time {
if c.TimeNow != nil {
return c.TimeNow()
}
return time.Now()
}
// GetToken returns the current token if its at least `minTTL` from expiration, or fetches a new one.
func (c *Cache) GetToken(ctx context.Context, minTTL time.Duration) (string, error) {
minExpiration := c.timeNow().Add(minTTL)
token, ok := c.token.Load().(*Token)
if ok && token.ExpiresAfter(minExpiration) {
return token.Bearer, nil
}
return c.forceRefreshToken(ctx, minExpiration)
}
func (c *Cache) forceRefreshToken(ctx context.Context, minExpiration time.Time) (string, error) {
select {
case c.lock <- struct{}{}:
case <-ctx.Done():
return "", ctx.Err()
}
defer func() {
<-c.lock
}()
ctx, cancel := context.WithTimeout(ctx, maxLockWait)
defer cancel()
token, ok := c.token.Load().(*Token)
if ok && token.ExpiresAfter(minExpiration) {
return token.Bearer, nil
}
token, err := c.fetcher(ctx, c.refreshToken)
if err != nil {
return "", err
}
c.token.Store(token)
if token.Expires.Before(minExpiration) {
return "", fmt.Errorf("new token cannot satisfy TTL: %v", minExpiration.Sub(token.Expires))
}
return token.Bearer, nil
}

View file

@ -0,0 +1,65 @@
package token_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/pkg/zero/token"
)
func TestCache(t *testing.T) {
t.Parallel()
t.Run("token expired, fetch new", func(t *testing.T) {
t.Parallel()
var testToken *token.Token
var testError error
fetcher := func(ctx context.Context, refreshToken string) (*token.Token, error) {
if testToken != nil {
token := *testToken
return &token, nil
}
return nil, testError
}
c := token.NewCache(fetcher, "test-refresh-token")
now := time.Now()
c.TimeNow = func() time.Time { return now }
testToken = &token.Token{"bearer-1", now.Add(time.Hour)}
bearer, err := c.GetToken(context.Background(), time.Minute)
require.NoError(t, err)
assert.Equal(t, "bearer-1", bearer)
now = now.Add(time.Minute * 30)
testToken.Bearer = "bearer-2"
// token is still valid, so we should get the same one
bearer, err = c.GetToken(context.Background(), time.Minute*20)
require.NoError(t, err)
assert.Equal(t, "bearer-1", bearer)
now = now.Add(time.Minute * 30)
testToken = &token.Token{"bearer-3", now.Add(time.Hour)}
bearer, err = c.GetToken(context.Background(), time.Minute*30)
require.NoError(t, err)
assert.Equal(t, "bearer-3", bearer)
})
t.Run("token cannot fit minTTL", func(t *testing.T) {
t.Parallel()
fetcher := func(ctx context.Context, refreshToken string) (*token.Token, error) {
return &token.Token{"ok-bearer", time.Now().Add(time.Minute)}, nil
}
c := token.NewCache(fetcher, "test-refresh-token")
_, err := c.GetToken(context.Background(), time.Minute*2)
assert.Error(t, err)
})
}