Compare commits

...

50 commits

Author SHA1 Message Date
Denis Mishin
4dd5357fe3
mcp: extend code usage (#5588) 2025-04-25 14:47:11 -04:00
Denis Mishin
9e4947c62f
mcp: authorize request (pt2) (#5586) 2025-04-24 12:11:19 -07:00
Denis Mishin
63ccf6ab93
mcp: authorize request (pt1) (#5585) 2025-04-24 14:59:12 -04:00
Denis Mishin
b566661353
mcp: client registration: store to the databroker (#5584) 2025-04-24 14:54:31 -04:00
Denis Mishin
9f4b03f916
mcp: add rfc7591 types (#5583) 2025-04-24 14:46:08 -04:00
Denis Mishin
db221cb826
mcp: storage scaffolding (#5581) 2025-04-23 13:39:27 -04:00
Denis Mishin
f1a9401ddc
mcp: scaffolding of /.pomerium/mcp routes (#5580) 2025-04-23 12:36:31 -04:00
Denis Mishin
cb0e8aaf06
mcp: add oauth metadata endpoint (#5579) 2025-04-23 12:24:00 -04:00
Kenneth Jenkins
2e7d1c7f12
authorize: refactor logAuthorizeCheck() (#5576)
Currently, policy evaluation and authorize logging are coupled to the
Envoy CheckRequest proto message (part of the ext_authz API). In the
context of ssh proxy authentication, we won't have a CheckRequest.
Instead, let's make the existing evaluator.Request type the source of
truth for the authorize log fields.

This way, whether we populate the evaluator.Request struct from an
ext_authz request or from an ssh proxy request, we can use the same
logAuthorizeCheck() method for logging.

Add some additional fields to evaluator.RequestHTTP for the authorize
log fields that are not currently represented in this struct.
2025-04-23 09:21:52 -07:00
Caleb Doxsey
8738066ce4
storage: add sync querier (#5570)
* storage: add fallback querier

* storage: add sync querier

* storage: add typed querier

* use synced querier
2025-04-23 10:15:48 -06:00
Kenneth Jenkins
e1d84a1dde
logging: standardize on hyphens in attribute names (#5577) 2025-04-22 10:57:19 -07:00
Denis Mishin
e71fca76f2
mcp: add to route config, 401 when unauthenticated (#5578) 2025-04-22 11:47:09 -04:00
Caleb Doxsey
a10b505386
add code of conduct (#5572) 2025-04-14 12:53:28 -06:00
Caleb Doxsey
395541775c
add CONTRIBUTING (#5573)
* add CONTRIBUTING

* fix url
2025-04-14 11:53:20 -07:00
Caleb Doxsey
e78cfc0687
cleanup logs (#5571) 2025-04-14 08:20:10 -06:00
Kenneth Jenkins
62addcf2a5
API changes for multi-domain login redirects (#5565)
Add a depends_on field to the Route proto and update the to/from 
conversion methods.
2025-04-11 14:56:16 -07:00
dependabot[bot]
4af9150b39
chore(deps): bump @babel/runtime from 7.24.4 to 7.26.10 in /ui (#5522)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.24.4 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-11 08:02:05 -06:00
Caleb Doxsey
3891293fa7
storage: add minimum record version hint (#5569)
* storage: add minimum record version hint

* use response record version

* fix record version in query response
2025-04-10 11:15:14 -06:00
Caleb Doxsey
cd731789be
storage: support ip address indexing for the in-memory store (#5568) 2025-04-10 08:21:52 -06:00
Denis Mishin
c7ffb95483
add v0.29.0 release notes (#5515) 2025-04-08 11:34:20 -04:00
Caleb Doxsey
a1eb75a8fe
add support for pomerium.request.headers for set_request_headers (#5563)
* add support for pomerium.request.headers for set_request_headers

* add peg grammar
2025-04-07 10:32:03 -06:00
dependabot[bot]
5f95dd32db
chore(deps): bump the go group with 39 updates (#5559)
* chore(deps): bump the go group with 39 updates

Bumps the go group with 39 updates:

| Package | From | To |
| --- | --- | --- |
| [cloud.google.com/go/storage](https://github.com/googleapis/google-cloud-go) | `1.50.0` | `1.51.0` |
| [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) | `1.29.8` | `1.29.12` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.78.0` | `1.79.0` |
| [github.com/bits-and-blooms/bitset](https://github.com/bits-and-blooms/bitset) | `1.21.0` | `1.22.0` |
| [github.com/caddyserver/certmagic](https://github.com/caddyserver/certmagic) | `0.21.7` | `0.22.2` |
| [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) | `3.12.0` | `3.13.0` |
| [github.com/docker/docker](https://github.com/docker/docker) | `28.0.1+incompatible` | `28.0.4+incompatible` |
| [github.com/grpc-ecosystem/go-grpc-middleware/v2](https://github.com/grpc-ecosystem/go-grpc-middleware) | `2.3.0` | `2.3.1` |
| [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) | `5.7.2` | `5.7.4` |
| [github.com/mholt/acmez/v3](https://github.com/mholt/acmez) | `3.0.1` | `3.1.1` |
| [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) | `7.0.87` | `7.0.89` |
| [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) | `1.2.0` | `1.3.0` |
| [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.21.0` | `1.21.1` |
| [github.com/prometheus/common](https://github.com/prometheus/common) | `0.62.0` | `0.63.0` |
| [github.com/prometheus/procfs](https://github.com/prometheus/procfs) | `0.15.1` | `0.16.0` |
| [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) | `0.50.0` | `0.50.1` |
| [github.com/rs/zerolog](https://github.com/rs/zerolog) | `1.33.0` | `1.34.0` |
| [github.com/spf13/viper](https://github.com/spf13/viper) | `1.19.0` | `1.20.1` |
| [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) | `0.35.0` | `0.36.0` |
| [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.59.0` | `0.60.0` |
| [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.59.0` | `0.60.0` |
| [go.opentelemetry.io/contrib/propagators/autoprop](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.59.0` | `0.60.0` |
| [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/bridge/opencensus](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/sdk/metric](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) | `1.34.0` | `1.35.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.37.0` | `0.38.0` |
| [golang.org/x/oauth2](https://github.com/golang/oauth2) | `0.27.0` | `0.28.0` |
| [golang.org/x/time](https://github.com/golang/time) | `0.10.0` | `0.11.0` |
| [google.golang.org/api](https://github.com/googleapis/google-api-go-client) | `0.223.0` | `0.224.0` |
| [google.golang.org/genproto/googleapis/rpc](https://github.com/googleapis/go-genproto) | `0.0.0-20250219182151-9fdb1cabc7b2` | `0.0.0-20250303144028-a0af3efb3deb` |
| [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.71.0` | `1.71.1` |
| google.golang.org/protobuf | `1.36.5` | `1.36.6` |


Updates `cloud.google.com/go/storage` from 1.50.0 to 1.51.0
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/spanner/v1.50.0...spanner/v1.51.0)

Updates `github.com/aws/aws-sdk-go-v2/config` from 1.29.8 to 1.29.12
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.29.8...config/v1.29.12)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.78.0 to 1.79.0
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.78.0...service/s3/v1.79.0)

Updates `github.com/bits-and-blooms/bitset` from 1.21.0 to 1.22.0
- [Release notes](https://github.com/bits-and-blooms/bitset/releases)
- [Commits](https://github.com/bits-and-blooms/bitset/compare/v1.21.0...v1.22.0)

Updates `github.com/caddyserver/certmagic` from 0.21.7 to 0.22.2
- [Release notes](https://github.com/caddyserver/certmagic/releases)
- [Commits](https://github.com/caddyserver/certmagic/compare/v0.21.7...v0.22.2)

Updates `github.com/coreos/go-oidc/v3` from 3.12.0 to 3.13.0
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.12.0...v3.13.0)

Updates `github.com/docker/docker` from 28.0.1+incompatible to 28.0.4+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.1...v28.0.4)

Updates `github.com/grpc-ecosystem/go-grpc-middleware/v2` from 2.3.0 to 2.3.1
- [Release notes](https://github.com/grpc-ecosystem/go-grpc-middleware/releases)
- [Commits](https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v2.3.0...v2.3.1)

Updates `github.com/jackc/pgx/v5` from 5.7.2 to 5.7.4
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.7.2...v5.7.4)

Updates `github.com/mholt/acmez/v3` from 3.0.1 to 3.1.1
- [Release notes](https://github.com/mholt/acmez/releases)
- [Commits](https://github.com/mholt/acmez/compare/v3.0.1...v3.1.1)

Updates `github.com/minio/minio-go/v7` from 7.0.87 to 7.0.89
- [Release notes](https://github.com/minio/minio-go/releases)
- [Commits](https://github.com/minio/minio-go/compare/v7.0.87...v7.0.89)

Updates `github.com/open-policy-agent/opa` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v1.2.0...v1.3.0)

Updates `github.com/prometheus/client_golang` from 1.21.0 to 1.21.1
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.21.0...v1.21.1)

Updates `github.com/prometheus/common` from 0.62.0 to 0.63.0
- [Release notes](https://github.com/prometheus/common/releases)
- [Changelog](https://github.com/prometheus/common/blob/main/RELEASE.md)
- [Commits](https://github.com/prometheus/common/compare/v0.62.0...v0.63.0)

Updates `github.com/prometheus/procfs` from 0.15.1 to 0.16.0
- [Release notes](https://github.com/prometheus/procfs/releases)
- [Commits](https://github.com/prometheus/procfs/compare/v0.15.1...v0.16.0)

Updates `github.com/quic-go/quic-go` from 0.50.0 to 0.50.1
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.50.0...v0.50.1)

Updates `github.com/rs/zerolog` from 1.33.0 to 1.34.0
- [Commits](https://github.com/rs/zerolog/compare/v1.33.0...v1.34.0)

Updates `github.com/spf13/viper` from 1.19.0 to 1.20.1
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.19.0...v1.20.1)

Updates `github.com/testcontainers/testcontainers-go` from 0.35.0 to 0.36.0
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.35.0...v0.36.0)

Updates `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` from 0.59.0 to 0.60.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.59.0...zpages/v0.60.0)

Updates `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` from 0.59.0 to 0.60.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.59.0...zpages/v0.60.0)

Updates `go.opentelemetry.io/contrib/propagators/autoprop` from 0.59.0 to 0.60.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.59.0...zpages/v0.60.0)

Updates `go.opentelemetry.io/otel` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/bridge/opencensus` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/metric` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/sdk` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/sdk/metric` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `go.opentelemetry.io/otel/trace` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

Updates `golang.org/x/net` from 0.37.0 to 0.38.0
- [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0)

Updates `golang.org/x/oauth2` from 0.27.0 to 0.28.0
- [Commits](https://github.com/golang/oauth2/compare/v0.27.0...v0.28.0)

Updates `golang.org/x/time` from 0.10.0 to 0.11.0
- [Commits](https://github.com/golang/time/compare/v0.10.0...v0.11.0)

Updates `google.golang.org/api` from 0.223.0 to 0.224.0
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.223.0...v0.224.0)

Updates `google.golang.org/genproto/googleapis/rpc` from 0.0.0-20250219182151-9fdb1cabc7b2 to 0.0.0-20250303144028-a0af3efb3deb
- [Commits](https://github.com/googleapis/go-genproto/commits)

Updates `google.golang.org/grpc` from 1.71.0 to 1.71.1
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.71.0...v1.71.1)

Updates `google.golang.org/protobuf` from 1.36.5 to 1.36.6

---
updated-dependencies:
- dependency-name: cloud.google.com/go/storage
  dependency-version: 1.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.29.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.79.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/bits-and-blooms/bitset
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/caddyserver/certmagic
  dependency-version: 0.22.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-version: 3.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/docker/docker
  dependency-version: 28.0.4+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/grpc-ecosystem/go-grpc-middleware/v2
  dependency-version: 2.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/jackc/pgx/v5
  dependency-version: 5.7.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/mholt/acmez/v3
  dependency-version: 3.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/minio/minio-go/v7
  dependency-version: 7.0.89
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/open-policy-agent/opa
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.21.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/prometheus/common
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/prometheus/procfs
  dependency-version: 0.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/rs/zerolog
  dependency-version: 1.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/spf13/viper
  dependency-version: 1.20.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/testcontainers/testcontainers-go
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-version: 0.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/contrib/propagators/autoprop
  dependency-version: 0.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/bridge/opencensus
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/metric
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/sdk/metric
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/oauth2
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/time
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: google.golang.org/api
  dependency-version: 0.224.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: google.golang.org/genproto/googleapis/rpc
  dependency-version: 0.0.0-20250303144028-a0af3efb3deb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: google.golang.org/grpc
  dependency-version: 1.71.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix build errors

* update OPA formatting in policy generator test

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
2025-04-04 16:26:51 -07:00
Kenneth Jenkins
c848c225e8
multi-domain login redirects (#5564)
Add a new 'depends_on' route configuration option taking a list of 
additional hosts to redirect through on login. Update the authorize 
service and proxy service to support a chain of /.pomerium/callback
redirects. Add an integration test for this feature.
2025-04-04 13:14:30 -07:00
Caleb Doxsey
c47055bece
upgrade to go v1.24 (#5562)
* upgrade to go v1.24

* add a macOS-specific //nolint comment too

---------

Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
2025-04-02 15:53:09 -06:00
dependabot[bot]
8d9f1bb38e
chore(deps): bump the github-actions group with 7 updates (#5557)
Bumps the github-actions group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [docker/login-action](https://github.com/docker/login-action) | `3.3.0` | `3.4.0` |
| [actions/setup-go](https://github.com/actions/setup-go) | `5.3.0` | `5.4.0` |
| [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) | `6.5.0` | `7.0.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `4.2.0` | `4.3.0` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `6.2.1` | `6.3.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.1` | `4.6.2` |
| [actions/setup-python](https://github.com/actions/setup-python) | `5.4.0` | `5.5.0` |


Updates `docker/login-action` from 3.3.0 to 3.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](9780b0c442...74a5d14239)

Updates `actions/setup-go` from 5.3.0 to 5.4.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](f111f3307d...0aaccfd150)

Updates `golangci/golangci-lint-action` from 6.5.0 to 7.0.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](2226d7cb06...1481404843)

Updates `actions/setup-node` from 4.2.0 to 4.3.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](1d0ff469b7...cdca7365b2)

Updates `goreleaser/goreleaser-action` from 6.2.1 to 6.3.0
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v6.2.1...v6.3.0)

Updates `actions/upload-artifact` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](4cec3d8aa0...ea165f8d65)

Updates `actions/setup-python` from 5.4.0 to 5.5.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](42375524e2...8d9ed9ac5c)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/setup-go
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: golangci/golangci-lint-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-python
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 12:44:49 -06:00
Caleb Doxsey
c5716a6045
remove debug log message for directories (#5560) 2025-04-02 10:17:42 -06:00
dependabot[bot]
9161cac1eb
chore(deps): bump the docker group with 3 updates (#5558)
Bumps the docker group with 3 updates: node, golang and distroless/base-debian12.


Updates `node` from `f6b9c31` to `c7fd844`

Updates `golang` from `d7d795d` to `fa1a01d`

Updates `distroless/base-debian12` from `3a59a8d` to `02be006`

---
updated-dependencies:
- dependency-name: node
  dependency-version: lts-bookworm
  dependency-type: direct:production
  dependency-group: docker
- dependency-name: golang
  dependency-version: 1.24-bookworm
  dependency-type: direct:production
  dependency-group: docker
- dependency-name: distroless/base-debian12
  dependency-version: debug
  dependency-type: direct:production
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 16:56:57 -06:00
Caleb Doxsey
e984d07a55
return errors according to accept header (#5551) 2025-04-01 08:36:00 -06:00
Kenneth Jenkins
ce46562a48
ci: build docker images for experimental/* branches (#5552) 2025-03-28 13:06:18 -07:00
Caleb Doxsey
1a199eb9f5
authenticate: remove /.pomerium/callback handler (#5553) 2025-03-28 13:04:25 -06:00
Denis Mishin
bed6770e16
ci: set goreleaser Node version to 22 (#5547) 2025-03-26 13:38:52 -04:00
Caleb Doxsey
38ca6d52b9
only support loading idp tokens via bearer tokens (#5545) 2025-03-26 09:47:40 -06:00
Kenneth Jenkins
b188a168af
metrics: fix an apparent metric setup error (#5543)
The IdentityManagerLastSessionRefreshErrorView appears to be a duplicate
of IdentityManagerLastUserRefreshErrorView. Adjust it to use the
matching identityManagerLastSessionRefreshError instead.
2025-03-25 14:48:07 -07:00
Caleb Doxsey
e7675a5b2a
databroker: preserve data type when deleting changeset (#5540)
* databroker: preserve data type when deleting changeset

* use cs.now
2025-03-25 10:11:36 -06:00
Joe Kralicky
a96ab2fe93
move internal/telemetry/trace => pkg/telemetry/trace (#5541) 2025-03-25 10:43:04 -04:00
Denis Mishin
ab5f3ac7f3
core/envoyconfig: make adding ipv6 addresses to internal cidr list conditional on ipv6 support on the system (#5538) 2025-03-21 11:14:50 -04:00
Caleb Doxsey
bc263e3ee5
proxy: use querier cache for user info (#5532) 2025-03-20 09:50:22 -06:00
Joe Kralicky
08623ef346
add tests/benchmarks for http1/http2 tcp tunnels and http1 websockets (#5471)
* add tests/benchmarks for http1/http2 tcp tunnels and http1 websockets

testenv:
- add new TCP upstream
- add websocket functions to HTTP upstream
- add https support to mock idp (default on)
- add new debug flags -env.bind-address and -env.use-trace-environ to
  allow changing the default bind address, and enabling otel environment
  based trace config, respectively

* linter pass

---------

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2025-03-19 18:42:19 -04:00
Caleb Doxsey
d6b02441b3
authorize: return 403 on invalid sessions (#5536) 2025-03-19 14:41:28 -06:00
dependabot[bot]
2795cc68aa
chore(deps): bump @babel/helpers from 7.24.4 to 7.26.10 in /ui (#5523)
Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.24.4 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 13:26:33 -06:00
Joe Kralicky
8c6955dbe2
Increase some test timeouts (#5535)
increase some test timeouts
2025-03-19 14:52:11 -04:00
Caleb Doxsey
4c9398e95b
config: fix layered bearer_token_format and idp_access_token_allowed_audiences (#5533) 2025-03-19 10:04:48 -06:00
Denis Mishin
5ef16bcd28
metrics: reduce gc pressure (#5530) 2025-03-18 13:48:49 -04:00
Kenneth Jenkins
562101ae03
remove the legacy identity manager (#5528) 2025-03-17 11:59:02 -07:00
dependabot[bot]
bdfc17d1ce
chore(deps): bump golang.org/x/net from 0.35.0 to 0.36.0 (#5526)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-14 13:43:44 -06:00
Denis Mishin
c4a5502f49
websockets: disable http2 connect (#5516) 2025-03-13 09:46:08 -04:00
Kenneth Jenkins
e1eca4e97c
config: fix jwt_issuer_format conversion (#5524)
Remove the previous conversion logic in NewPolicyFromProto() for the 
jwt_issuer_format field. This would prevent the new "unset" state from
working correctly. Add a unit test to verify that all three values
(unset, "hostOnly" and "uri") will successfully round trip to the proto
format and back again.

Also add a test case for the Options.ApplySettings() method to verify 
that an unset jwt_issuer_format will not overwrite the existing value
(if any) in the settings.
2025-03-12 16:13:16 -07:00
Denis Mishin
9cd5160468
zero/grpc: use hostname for proxied grpc calls (#5520) 2025-03-11 17:37:01 -04:00
Kenneth Jenkins
ad183873f4
add global jwt_issuer_format option (#5508)
Add a corresponding global setting for the existing route-level
jwt_issuer_format option. The route-level option will take precedence
when set to a non-empty string.
2025-03-11 14:11:50 -07:00
Denis Mishin
b86c9931b1
testutil: use cmp.Diff in protobuf json assertion (#5517) 2025-03-07 20:20:27 -05:00
265 changed files with 10374 additions and 4620 deletions

83
.github/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,83 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [info@pomerium.io](mailto:info@pomerium.io). All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

80
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,80 @@
# Contributing
First of all, thank you for considering contributing to Pomerium! You can have a direct impact on Pomerium by helping with its code or documentation.
- To contribute to Pomerium, open a [pull request](https://github.com/pomerium/pomerium/pulls) (PR) to the Pomerium repository.
- To contribute to the documentation, open a [pull request](https://github.com/pomerium/documentation/pulls) (PR) to the documentation repository.
If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.**
## General
We try to hold contributions to a high standard for quality, so don't be surprised if we ask for revisions--even if it seems small or insignificant. Please don't take it personally. If your change is on the right track, we can guide you to make it mergeable.
Here are some of the expectations we have of contributors:
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Pomerium is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Docs
Pomerium's documentation is available at <https://www.pomerium.com/docs>. If you find a typo, feel a section could be better described, or have an idea for a totally new application or section, don't hesitate to make a pull request change. There are few ways you can do this.
### Simple edits
The easiest way to fix minor documentation issues in Pomerium is to click on "Edit this page in Github" on any page.
Doing so will create a [fork](https://help.github.com/en/articles/fork-a-repo) of the project, allow you to [update the page](https://guides.github.com/features/mastering-markdown/), and create a [pull request](https://help.github.com/en/articles/about-pull-requests).
### Bigger changes
If you need to add a new page, or would like greater control over the editing process you can edit the docs similar to how you would make changes to the source code.
#### Pre-reqs
Before building the docs, you'll need to install the following pre-requisites.
1. [Node.js](https://nodejs.org/en/download/).
2. [Yarn](https://yarnpkg.com/lang/en/docs).
#### Make changes
Once you have Nodejs and Yarn installed, simply run `yarn start` in a terminal which will install any required node packages as well as start up a development server. You should see something like the below, with a link to the local doc server.
```bash
[SUCCESS] Docusaurus website is running at: http://localhost:3001/
```
Once you have the development server up and running, any changes you make will automatically be reloaded and accessible in your browser.
### PR Previews
We use [Netlify](https://www.netlify.com) to build and host our docs. One of nice features of Netlify, is that a preview of the docs are automatically created for each new pull request that is made, which lets you be sure that the version of your docs that you see locally match what will ultimately be deployed in production.
[configuration variables]: /docs/reference
[download]: https://github.com/pomerium/pomerium/releases
[environmental configuration variables]: https://12factor.net/config
[verify]: https://verify.pomerium.com/
[identity provider]: /docs/identity-providers
[make]: https://en.wikipedia.org/wiki/Make_(software)
[tls certificates]: /docs/concepts/certificates

View file

@ -24,7 +24,7 @@ jobs:
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View file

@ -5,6 +5,7 @@ on:
push:
branches:
- 0-[0-9]+-*
- experimental/*
jobs:
publish:
@ -32,7 +33,7 @@ jobs:
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View file

@ -13,14 +13,14 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
with:
go-version: 1.23.x
go-version: 1.24.x
cache: false
- run: make deps-build
- uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837
- uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84
with:
version: v1.60.1
version: v1.64.8
args: --timeout=10m

View file

@ -26,14 +26,14 @@ jobs:
run: git fetch --prune --unshallow
- name: Set up Node.js
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
with:
node-version: 16.x
node-version: 22.x
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
with:
go-version: 1.23.x
go-version: 1.24.x
cache: false
- name: Set up Docker
@ -57,7 +57,7 @@ jobs:
run: gcloud auth configure-docker
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6.2.1
uses: goreleaser/goreleaser-action@v6.3.0
with:
version: v0.184.0
args: release --config .github/goreleaser.yaml
@ -99,7 +99,7 @@ jobs:
echo "tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
- name: Publish latest tag
if: "steps.latestTag.outputs.tag == steps.tagName.outputs.tag"
if: steps.latestTag.outputs.tag == steps.tagName.outputs.tag
run: |
docker manifest create -a pomerium/pomerium:latest pomerium/pomerium:amd64-${{ steps.tagName.outputs.tag }} pomerium/pomerium:arm64v8-${{ steps.tagName.outputs.tag }}
docker manifest push pomerium/pomerium:latest

View file

@ -12,7 +12,6 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version: [1.23.x]
node-version: [22.x]
platform: [ubuntu-22.04]
deployment: [multi, single]
@ -21,12 +20,12 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
with:
go-version: ${{ matrix.go-version }}
go-version: 1.24.x
cache: false
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
with:
node-version: ${{ matrix.node-version }}
cache: yarn
@ -60,11 +59,11 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
with:
go-version: ${{ matrix.go-version }}
go-version: 1.24.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e
with:
node-version: ${{ matrix.node-version }}
cache: yarn
@ -76,7 +75,7 @@ jobs:
make build
- name: save binary
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
path: bin/pomerium*
name: pomerium ${{ github.run_id }} ${{ matrix.platform }}
@ -124,12 +123,12 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
with:
go-version: 1.23.x
go-version: 1.24.x
cache: false
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
with:
python-version: "3.x"

View file

@ -11,11 +11,11 @@ linters:
enable:
- asasalint
- bodyclose
- copyloopvar
- dogsled
- errcheck
- errorlint
- exportloopref
# - gci # https://github.com/daixiang0/gci/issues/209
- gci
- gocheckcompilerdirectives
- gofumpt
- goimports
@ -30,10 +30,10 @@ linters:
- revive
- staticcheck
- stylecheck
- tenv
- unconvert
- unused
- usestdlibvars
- usetesting
issues:
# List of regexps of issue texts to exclude, empty list by default.

View file

@ -1,2 +0,0 @@
golang 1.23.0
golangci-lint 1.60.1

View file

@ -1,4 +1,4 @@
FROM node:lts-bookworm@sha256:f6b9c31ace05502dd98ef777aaa20464362435dcc5e312b0e213121dcf7d8b95 AS ui
FROM node:lts-bookworm@sha256:c7fd844945a76eeaa83cb372e4d289b4a30b478a1c80e16c685b62c54156285b AS ui
WORKDIR /build
COPY .git ./.git
@ -13,7 +13,7 @@ RUN make yarn
COPY ./ui/ ./ui/
RUN make build-ui
FROM golang:1.24-bookworm@sha256:d7d795d0a9f51b00d9c9bfd17388c2c626004a50c6ed7c581e095122507fe1ab AS build
FROM golang:1.24-bookworm@sha256:fa1a01d362a7b9df68b021d59a124d28cae6d99ebd1a876e3557c4dd092f1b1d AS build
WORKDIR /go/src/github.com/pomerium/pomerium
RUN apt-get update \
@ -29,7 +29,7 @@ COPY --from=ui /build/ui/dist ./ui/dist
RUN make build-go NAME=pomerium
RUN touch /config.yaml
FROM gcr.io/distroless/base-debian12:debug@sha256:3a59a8d10471fc8487fd2ca93746b0195ed4c3236c14fe8412cf7b2ec4b8c1f3
FROM gcr.io/distroless/base-debian12:debug@sha256:02be0066ee51d3d8a77be84e7136df6b9946c46e114aa2ffc5f08027cc5840ff
ENV AUTOCERT_DIR=/data/autocert
WORKDIR /pomerium
COPY --from=build /go/src/github.com/pomerium/pomerium/bin/* /bin/

View file

@ -1,4 +1,4 @@
FROM node:lts-bookworm@sha256:f6b9c31ace05502dd98ef777aaa20464362435dcc5e312b0e213121dcf7d8b95 AS ui
FROM node:lts-bookworm@sha256:c7fd844945a76eeaa83cb372e4d289b4a30b478a1c80e16c685b62c54156285b AS ui
WORKDIR /build
COPY .git ./.git
@ -13,7 +13,7 @@ RUN make yarn
COPY ./ui/ ./ui/
RUN make build-ui
FROM golang:1.24-bookworm@sha256:d7d795d0a9f51b00d9c9bfd17388c2c626004a50c6ed7c581e095122507fe1ab AS build
FROM golang:1.24-bookworm@sha256:fa1a01d362a7b9df68b021d59a124d28cae6d99ebd1a876e3557c4dd092f1b1d AS build
WORKDIR /go/src/github.com/pomerium/pomerium
RUN apt-get update \

View file

@ -90,9 +90,10 @@ build-ui: yarn
@cd ui; yarn build
.PHONY: lint
lint: ## Verifies `golint` passes.
@echo "==> $@"
@go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.1 run ./... --fix
lint:
@echo "@==> $@"
@VERSION=$$(go run github.com/mikefarah/yq/v4@v4.34.1 '.jobs.lint.steps[] | select(.uses == "golangci/golangci-lint-action*") | .with.version' .github/workflows/lint.yaml) && \
go run github.com/golangci/golangci-lint/cmd/golangci-lint@$$VERSION run ./... --fix
.PHONY: test
test: get-envoy ## Runs the go tests.

View file

@ -7,12 +7,13 @@ import (
"errors"
"fmt"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// ValidateOptions checks that configuration are complete and valid.

View file

@ -58,7 +58,6 @@ func TestOptions_Validate(t *testing.T) {
{"empty callback path", badCallbackPath, true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if err := ValidateOptions(tt.o); (err != nil) != tt.wantErr {
t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.wantErr)
@ -105,7 +104,6 @@ func TestNew(t *testing.T) {
{"bad signing key", badSigningKey, true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
_, err := New(context.Background(), &config.Config{Options: tt.opts})
if (err != nil) != tt.wantErr {

View file

@ -3,11 +3,12 @@ package authenticate
import (
"context"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/authenticate/events"
"github.com/pomerium/pomerium/config"
identitypb "github.com/pomerium/pomerium/pkg/grpc/identity"
"github.com/pomerium/pomerium/pkg/identity"
oteltrace "go.opentelemetry.io/otel/trace"
)
type authenticateConfig struct {

View file

@ -25,11 +25,11 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/middleware"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/oidc"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Handler returns the authenticate service's handler chain.
@ -117,9 +117,6 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
handlers.DeviceEnrolled(a.getUserInfoData(r)).ServeHTTP(w, r)
return nil
}))
cr := sr.PathPrefix("/callback").Subrouter()
cr.Path("/").Handler(a.requireValidSignature(a.Callback)).Methods(http.MethodGet)
}
// RetrieveSession is the middleware used retrieve session by the sessionLoader
@ -141,7 +138,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
if err != nil {
log.FromRequest(r).Info().
Err(err).
Str("idp_id", idpID).
Str("idp-id", idpID).
Msg("authenticate: session load error")
span.AddEvent("session load error",
oteltrace.WithAttributes(attribute.String("error", err.Error())))
@ -150,8 +147,8 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
if sessionState.IdentityProviderID != idpID {
log.FromRequest(r).Info().
Str("idp_id", idpID).
Str("session_idp_id", sessionState.IdentityProviderID).
Str("idp-id", idpID).
Str("session-idp-id", sessionState.IdentityProviderID).
Str("id", sessionState.ID).
Msg("authenticate: session not associated with identity provider")
span.AddEvent("session not associated with identity provider")
@ -161,7 +158,7 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
if err := state.flow.VerifySession(ctx, r, sessionState); err != nil {
log.FromRequest(r).Info().
Err(err).
Str("idp_id", idpID).
Str("idp-id", idpID).
Msg("authenticate: couldn't verify session")
span.AddEvent("couldn't verify session",
oteltrace.WithAttributes(attribute.String("error", err.Error())))
@ -526,54 +523,6 @@ func (a *Authenticate) revokeSession(ctx context.Context, w http.ResponseWriter,
return state.flow.RevokeSession(ctx, r, authenticator, sessionState)
}
// Callback handles the result of a successful call to the authenticate service
// and is responsible setting per-route sessions.
func (a *Authenticate) Callback(w http.ResponseWriter, r *http.Request) error {
redirectURLString := r.FormValue(urlutil.QueryRedirectURI)
encryptedSession := r.FormValue(urlutil.QuerySessionEncrypted)
redirectURL, err := urlutil.ParseAndValidateURL(redirectURLString)
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
rawJWT, err := a.saveCallbackSession(w, r, encryptedSession)
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
// if programmatic, encode the session jwt as a query param
if isProgrammatic := r.FormValue(urlutil.QueryIsProgrammatic); isProgrammatic == "true" {
q := redirectURL.Query()
q.Set(urlutil.QueryPomeriumJWT, string(rawJWT))
redirectURL.RawQuery = q.Encode()
}
httputil.Redirect(w, r, redirectURL.String(), http.StatusFound)
return nil
}
// saveCallbackSession takes an encrypted per-route session token, decrypts
// it using the shared service key, then stores it the local session store.
func (a *Authenticate) saveCallbackSession(w http.ResponseWriter, r *http.Request, enctoken string) ([]byte, error) {
state := a.state.Load()
// 1. extract the base64 encoded and encrypted JWT from query params
encryptedJWT, err := base64.URLEncoding.DecodeString(enctoken)
if err != nil {
return nil, fmt.Errorf("proxy: malfromed callback token: %w", err)
}
// 2. decrypt the JWT using the cipher using the _shared_ secret key
rawJWT, err := cryptutil.Decrypt(state.sharedCipher, encryptedJWT, nil)
if err != nil {
return nil, fmt.Errorf("proxy: callback token decrypt error: %w", err)
}
// 3. Save the decrypted JWT to the session store directly as a string, without resigning
if err = state.sessionStore.SaveSession(w, r, rawJWT); err != nil {
return nil, fmt.Errorf("proxy: callback session save failure: %w", err)
}
return rawJWT, nil
}
func (a *Authenticate) getIdentityProviderIDForRequest(r *http.Request) string {
if err := r.ParseForm(); err != nil {
return ""

View file

@ -219,7 +219,6 @@ func TestAuthenticate_SignOut(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@ -343,7 +342,6 @@ func TestAuthenticate_OAuthCallback(t *testing.T) {
{"bad hmac", http.MethodGet, time.Now().Unix(), base64.URLEncoding.EncodeToString([]byte("malformed_state")), "", "", "", "code", "https://corp.pomerium.io", "https://authenticate.pomerium.io", &mstore.Store{}, identity.MockProvider{AuthenticateResponse: oauth2.Token{}}, "https://corp.pomerium.io", http.StatusBadRequest},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@ -449,7 +447,6 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@ -694,14 +691,6 @@ func (m mockDataBrokerServiceClient) Put(ctx context.Context, in *databroker.Put
return m.put(ctx, in, opts...)
}
func mustParseURL(rawurl string) *url.URL {
u, err := url.Parse(rawurl)
if err != nil {
panic(err)
}
return u
}
// stubFlow is a stub implementation of the flow interface.
type stubFlow struct {
verifySignatureErr error

View file

@ -20,14 +20,3 @@ func (a *Authenticate) requireValidSignatureOnRedirect(next httputil.HandlerFunc
return next(w, r)
})
}
// requireValidSignature validates the pomerium_signature.
func (a *Authenticate) requireValidSignature(next httputil.HandlerFunc) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
err := a.state.Load().flow.VerifyAuthenticateSignature(r)
if err != nil {
return err
}
return next(w, r)
})
}

View file

@ -6,33 +6,28 @@ import (
"context"
"fmt"
"slices"
"time"
"github.com/rs/zerolog"
oteltrace "go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"github.com/pomerium/datasource/pkg/directory"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Authorize struct holds
type Authorize struct {
state *atomicutil.Value[*authorizeState]
store *store.Store
currentConfig *atomicutil.Value[*config.Config]
accessTracker *AccessTracker
globalCache storage.Cache
groupsCacheWarmer *cacheWarmer
state *atomicutil.Value[*authorizeState]
store *store.Store
currentConfig *atomicutil.Value[*config.Config]
accessTracker *AccessTracker
tracerProvider oteltrace.TracerProvider
tracer oteltrace.Tracer
@ -43,21 +38,19 @@ func New(ctx context.Context, cfg *config.Config) (*Authorize, error) {
tracerProvider := trace.NewTracerProvider(ctx, "Authorize")
tracer := tracerProvider.Tracer(trace.PomeriumCoreTracer)
a := &Authorize{
currentConfig: atomicutil.NewValue(&config.Config{Options: new(config.Options)}),
currentConfig: atomicutil.NewValue(cfg),
store: store.New(),
globalCache: storage.NewGlobalCache(time.Minute),
tracerProvider: tracerProvider,
tracer: tracer,
}
a.accessTracker = NewAccessTracker(a, accessTrackerMaxSize, accessTrackerDebouncePeriod)
state, err := newAuthorizeStateFromConfig(ctx, tracerProvider, cfg, a.store, nil)
state, err := newAuthorizeStateFromConfig(ctx, nil, tracerProvider, cfg, a.store)
if err != nil {
return nil, err
}
a.state = atomicutil.NewValue(state)
a.groupsCacheWarmer = newCacheWarmer(state.dataBrokerClientConnection, a.globalCache, directory.GroupRecordType)
return a, nil
}
@ -73,10 +66,6 @@ func (a *Authorize) Run(ctx context.Context) error {
a.accessTracker.Run(ctx)
return nil
})
eg.Go(func() error {
a.groupsCacheWarmer.Run(ctx)
return nil
})
return eg.Wait()
}
@ -149,6 +138,7 @@ func newPolicyEvaluator(
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
evaluator.WithJWTClaimsHeaders(opts.JWTClaimsHeaders),
evaluator.WithJWTGroupsFilter(opts.JWTGroupsFilter),
evaluator.WithDefaultJWTIssuerFormat(opts.JWTIssuerFormat),
)
}
@ -156,13 +146,9 @@ func newPolicyEvaluator(
func (a *Authorize) OnConfigChange(ctx context.Context, cfg *config.Config) {
currentState := a.state.Load()
a.currentConfig.Store(cfg)
if newState, err := newAuthorizeStateFromConfig(ctx, a.tracerProvider, cfg, a.store, currentState.evaluator); err != nil {
if newState, err := newAuthorizeStateFromConfig(ctx, currentState, a.tracerProvider, cfg, a.store); err != nil {
log.Ctx(ctx).Error().Err(err).Msg("authorize: error updating state")
} else {
a.state.Store(newState)
if currentState.dataBrokerClientConnection != newState.dataBrokerClientConnection {
a.groupsCacheWarmer.UpdateConn(newState.dataBrokerClientConnection)
}
}
}

View file

@ -79,7 +79,6 @@ func TestNew(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := New(context.Background(), &config.Config{Options: &tt.config})
@ -104,7 +103,6 @@ func TestAuthorize_OnConfigChange(t *testing.T) {
{"bad option", "gXK6ggrlIW2HyKyUF9rUO4azrDgxhDPWqw9y+lJU7B8=", policies, false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
o := &config.Options{

View file

@ -1,122 +0,0 @@
package authorize
import (
"context"
"time"
"google.golang.org/grpc"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
)
type cacheWarmer struct {
cc *grpc.ClientConn
cache storage.Cache
typeURL string
updatedCC chan *grpc.ClientConn
}
func newCacheWarmer(
cc *grpc.ClientConn,
cache storage.Cache,
typeURL string,
) *cacheWarmer {
return &cacheWarmer{
cc: cc,
cache: cache,
typeURL: typeURL,
updatedCC: make(chan *grpc.ClientConn, 1),
}
}
func (cw *cacheWarmer) UpdateConn(cc *grpc.ClientConn) {
for {
select {
case cw.updatedCC <- cc:
return
default:
}
select {
case <-cw.updatedCC:
default:
}
}
}
func (cw *cacheWarmer) Run(ctx context.Context) {
// Run a syncer for the cache warmer until the underlying databroker connection is changed.
// When that happens cancel the currently running syncer and start a new one.
runCtx, runCancel := context.WithCancel(ctx)
go cw.run(runCtx, cw.cc)
for {
select {
case <-ctx.Done():
runCancel()
return
case cc := <-cw.updatedCC:
log.Ctx(ctx).Info().Msg("cache-warmer: received updated databroker client connection, restarting syncer")
cw.cc = cc
runCancel()
runCtx, runCancel = context.WithCancel(ctx)
go cw.run(runCtx, cw.cc)
}
}
}
func (cw *cacheWarmer) run(ctx context.Context, cc *grpc.ClientConn) {
log.Ctx(ctx).Debug().Str("type-url", cw.typeURL).Msg("cache-warmer: running databroker syncer to warm cache")
_ = databroker.NewSyncer(ctx, "cache-warmer", cacheWarmerSyncerHandler{
client: databroker.NewDataBrokerServiceClient(cc),
cache: cw.cache,
}, databroker.WithTypeURL(cw.typeURL)).Run(ctx)
}
type cacheWarmerSyncerHandler struct {
client databroker.DataBrokerServiceClient
cache storage.Cache
}
func (h cacheWarmerSyncerHandler) GetDataBrokerServiceClient() databroker.DataBrokerServiceClient {
return h.client
}
func (h cacheWarmerSyncerHandler) ClearRecords(_ context.Context) {
h.cache.InvalidateAll()
}
func (h cacheWarmerSyncerHandler) UpdateRecords(ctx context.Context, serverVersion uint64, records []*databroker.Record) {
for _, record := range records {
req := &databroker.QueryRequest{
Type: record.Type,
Limit: 1,
}
req.SetFilterByIDOrIndex(record.Id)
res := &databroker.QueryResponse{
Records: []*databroker.Record{record},
TotalCount: 1,
ServerVersion: serverVersion,
RecordVersion: record.Version,
}
expiry := time.Now().Add(time.Hour * 24 * 365)
key, err := storage.MarshalQueryRequest(req)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("cache-warmer: failed to marshal query request")
continue
}
value, err := storage.MarshalQueryResponse(res)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("cache-warmer: failed to marshal query response")
continue
}
h.cache.Set(expiry, key, value)
}
}

View file

@ -1,52 +0,0 @@
package authorize
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
"google.golang.org/grpc"
"github.com/pomerium/pomerium/internal/databroker"
"github.com/pomerium/pomerium/internal/testutil"
databrokerpb "github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/protoutil"
"github.com/pomerium/pomerium/pkg/storage"
)
func TestCacheWarmer(t *testing.T) {
t.Parallel()
ctx := testutil.GetContext(t, 10*time.Minute)
cc := testutil.NewGRPCServer(t, func(srv *grpc.Server) {
databrokerpb.RegisterDataBrokerServiceServer(srv, databroker.New(ctx, noop.NewTracerProvider()))
})
t.Cleanup(func() { cc.Close() })
client := databrokerpb.NewDataBrokerServiceClient(cc)
_, err := client.Put(ctx, &databrokerpb.PutRequest{
Records: []*databrokerpb.Record{
{Type: "example.com/record", Id: "e1", Data: protoutil.NewAnyBool(true)},
{Type: "example.com/record", Id: "e2", Data: protoutil.NewAnyBool(true)},
},
})
require.NoError(t, err)
cache := storage.NewGlobalCache(time.Minute)
cw := newCacheWarmer(cc, cache, "example.com/record")
go cw.Run(ctx)
assert.Eventually(t, func() bool {
req := &databrokerpb.QueryRequest{
Type: "example.com/record",
Limit: 1,
}
req.SetFilterByIDOrIndex("e1")
res, err := storage.NewCachingQuerier(storage.NewStaticQuerier(), cache).Query(ctx, req)
require.NoError(t, err)
return len(res.GetRecords()) == 1
}, 10*time.Second, time.Millisecond*100)
}

View file

@ -19,6 +19,7 @@ import (
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"github.com/pomerium/pomerium/authorize/checkrequest"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
@ -98,6 +99,9 @@ func (a *Authorize) handleResultDenied(
case invalidClientCertReason(reasons):
denyStatusCode = httputil.StatusInvalidClientCertificate
denyStatusText = httputil.DetailsText(httputil.StatusInvalidClientCertificate)
case request.Policy.IsMCP():
denyStatusCode = http.StatusUnauthorized
denyStatusText = httputil.DetailsText(http.StatusUnauthorized)
}
return a.deniedResponse(ctx, in, denyStatusCode, denyStatusText, nil)
@ -158,7 +162,7 @@ func (a *Authorize) deniedResponse(
"code": code, // http code
})
headers.Set("Content-Type", "application/json")
case getCheckRequestURL(in).Path == "/robots.txt":
case checkrequest.GetURL(in).Path == "/robots.txt":
code = 200
respBody = []byte("User-agent: *\nDisallow: /")
headers.Set("Content-Type", "text/plain")
@ -216,7 +220,7 @@ func (a *Authorize) requireLoginResponse(
options := a.currentConfig.Load().Options
state := a.state.Load()
if !a.shouldRedirect(in) {
if !a.shouldRedirect(in, request) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, "Unauthenticated", nil)
}
@ -226,7 +230,7 @@ func (a *Authorize) requireLoginResponse(
}
// always assume https scheme
checkRequestURL := getCheckRequestURL(in)
checkRequestURL := checkrequest.GetURL(in)
checkRequestURL.Scheme = "https"
var signInURLQuery url.Values
@ -235,8 +239,12 @@ func (a *Authorize) requireLoginResponse(
signInURLQuery = url.Values{}
signInURLQuery.Add("pomerium_traceparent", id)
}
var additionalHosts []string
if request.Policy != nil {
additionalHosts = request.Policy.DependsOn
}
redirectTo, err := state.authenticateFlow.AuthenticateSignInURL(
ctx, signInURLQuery, &checkRequestURL, idp.GetId())
ctx, signInURLQuery, &checkRequestURL, idp.GetId(), additionalHosts)
if err != nil {
return nil, err
}
@ -255,7 +263,7 @@ func (a *Authorize) requireWebAuthnResponse(
state := a.state.Load()
// always assume https scheme
checkRequestURL := getCheckRequestURL(in)
checkRequestURL := checkrequest.GetURL(in)
checkRequestURL.Scheme = "https"
// If we're already on a webauthn route, return OK.
@ -264,7 +272,7 @@ func (a *Authorize) requireWebAuthnResponse(
return a.okResponse(result.Headers), nil
}
if !a.shouldRedirect(in) {
if !a.shouldRedirect(in, request) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, "Unauthenticated", nil)
}
@ -349,7 +357,11 @@ func (a *Authorize) userInfoEndpointURL(in *envoy_service_auth_v3.CheckRequest)
return urlutil.NewSignedURL(a.state.Load().sharedKey, debugEndpoint).Sign(), nil
}
func (a *Authorize) shouldRedirect(in *envoy_service_auth_v3.CheckRequest) bool {
func (a *Authorize) shouldRedirect(in *envoy_service_auth_v3.CheckRequest, request *evaluator.Request) bool {
if request.Policy.IsMCP() {
return false
}
requestHeaders := in.GetAttributes().GetRequest().GetHttp().GetHeaders()
if requestHeaders == nil {
return true

View file

@ -113,6 +113,18 @@ func TestAuthorize_handleResult(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 495, int(res.GetDeniedResponse().GetStatus().GetCode()))
})
t.Run("mcp-route-unauthenticated", func(t *testing.T) {
res, err := a.handleResult(context.Background(),
&envoy_service_auth_v3.CheckRequest{},
&evaluator.Request{
Policy: &config.Policy{MCP: &config.MCP{}},
},
&evaluator.Result{
Allow: evaluator.NewRuleResult(false, criteria.ReasonUserUnauthenticated),
})
assert.NoError(t, err)
assert.Equal(t, 401, int(res.GetDeniedResponse().GetStatus().GetCode()))
})
}
func TestAuthorize_okResponse(t *testing.T) {

View file

@ -0,0 +1,44 @@
// Package checkrequest contains helper functions for working with Envoy
// ext_authz CheckRequest messages.
package checkrequest
import (
"net/url"
"strings"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/urlutil"
)
// GetURL converts the request URL from an ext_authz CheckRequest to a [url.URL].
func GetURL(req *envoy_service_auth_v3.CheckRequest) url.URL {
h := req.GetAttributes().GetRequest().GetHttp()
u := url.URL{
Scheme: h.GetScheme(),
Host: h.GetHost(),
}
u.Host = urlutil.GetDomainsForURL(&u, false)[0]
// envoy sends the query string as part of the path
path := h.GetPath()
if idx := strings.Index(path, "?"); idx != -1 {
u.RawPath, u.RawQuery = path[:idx], path[idx+1:]
u.RawQuery = u.Query().Encode()
} else {
u.RawPath = path
}
u.Path, _ = url.PathUnescape(u.RawPath)
return u
}
// GetHeaders returns the HTTP headers from an ext_authz CheckRequest, canonicalizing
// the header keys.
func GetHeaders(req *envoy_service_auth_v3.CheckRequest) map[string]string {
hdrs := make(map[string]string)
ch := req.GetAttributes().GetRequest().GetHttp().GetHeaders()
for k, v := range ch {
hdrs[httputil.CanonicalHeaderKey(k)] = v
}
return hdrs
}

View file

@ -0,0 +1,55 @@
package checkrequest
import (
"net/url"
"testing"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/stretchr/testify/assert"
)
func TestGetURL(t *testing.T) {
req := &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Host: "example.com:80",
Path: "/some/path?a=b",
Scheme: "http",
Method: "GET",
Headers: map[string]string{"X-Request-Id": "CHECK-REQUEST-ID"},
},
},
},
}
assert.Equal(t, url.URL{
Scheme: "http",
Host: "example.com",
Path: "/some/path",
RawPath: "/some/path",
RawQuery: "a=b",
}, GetURL(req))
}
func TestGetHeaders(t *testing.T) {
req := &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Headers: map[string]string{
"content-type": "application/www-x-form-urlencoded",
"x-request-id": "CHECK-REQUEST-ID",
":authority": "example.com",
},
},
},
},
}
assert.Equal(t, map[string]string{
"Content-Type": "application/www-x-form-urlencoded",
"X-Request-Id": "CHECK-REQUEST-ID",
":authority": "example.com",
}, GetHeaders(req))
}

View file

@ -3,9 +3,6 @@ package authorize
import (
"context"
"google.golang.org/grpc"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/grpcutil"
@ -18,47 +15,6 @@ type sessionOrServiceAccount interface {
Validate() error
}
func getDataBrokerRecord(
ctx context.Context,
recordType string,
recordID string,
lowestRecordVersion uint64,
) (*databroker.Record, error) {
q := storage.GetQuerier(ctx)
req := &databroker.QueryRequest{
Type: recordType,
Limit: 1,
}
req.SetFilterByIDOrIndex(recordID)
res, err := q.Query(ctx, req, grpc.WaitForReady(true))
if err != nil {
return nil, err
}
if len(res.GetRecords()) == 0 {
return nil, storage.ErrNotFound
}
// if the current record version is less than the lowest we'll accept, invalidate the cache
if res.GetRecords()[0].GetVersion() < lowestRecordVersion {
q.InvalidateCache(ctx, req)
} else {
return res.GetRecords()[0], nil
}
// retry with an up to date cache
res, err = q.Query(ctx, req)
if err != nil {
return nil, err
}
if len(res.GetRecords()) == 0 {
return nil, storage.ErrNotFound
}
return res.GetRecords()[0], nil
}
func (a *Authorize) getDataBrokerSessionOrServiceAccount(
ctx context.Context,
sessionID string,
@ -67,9 +23,9 @@ func (a *Authorize) getDataBrokerSessionOrServiceAccount(
ctx, span := a.tracer.Start(ctx, "authorize.getDataBrokerSessionOrServiceAccount")
defer span.End()
record, err := getDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(session.Session)), sessionID, dataBrokerRecordVersion)
record, err := storage.GetDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(session.Session)), sessionID, dataBrokerRecordVersion)
if storage.IsNotFound(err) {
record, err = getDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(user.ServiceAccount)), sessionID, dataBrokerRecordVersion)
record, err = storage.GetDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(user.ServiceAccount)), sessionID, dataBrokerRecordVersion)
}
if err != nil {
return nil, err
@ -100,7 +56,7 @@ func (a *Authorize) getDataBrokerUser(
ctx, span := a.tracer.Start(ctx, "authorize.getDataBrokerUser")
defer span.End()
record, err := getDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(user.User)), userID, 0)
record, err := storage.GetDataBrokerRecord(ctx, grpcutil.GetTypeURL(new(user.User)), userID, 0)
if err != nil {
return nil, err
}

View file

@ -2,7 +2,6 @@ package authorize
import (
"context"
"fmt"
"testing"
"time"
@ -12,45 +11,9 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/storage"
)
func Test_getDataBrokerRecord(t *testing.T) {
t.Parallel()
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*10)
t.Cleanup(clearTimeout)
for _, tc := range []struct {
name string
recordVersion, queryVersion uint64
underlyingQueryCount, cachedQueryCount int
}{
{"cached", 1, 1, 1, 2},
{"invalidated", 1, 2, 3, 4},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
s1 := &session.Session{Id: "s1", Version: fmt.Sprint(tc.recordVersion)}
sq := storage.NewStaticQuerier(s1)
cq := storage.NewCachingQuerier(sq, storage.NewGlobalCache(time.Minute))
qctx := storage.WithQuerier(ctx, cq)
s, err := getDataBrokerRecord(qctx, grpcutil.GetTypeURL(s1), s1.GetId(), tc.queryVersion)
assert.NoError(t, err)
assert.NotNil(t, s)
s, err = getDataBrokerRecord(qctx, grpcutil.GetTypeURL(s1), s1.GetId(), tc.queryVersion)
assert.NoError(t, err)
assert.NotNil(t, s)
})
}
}
func TestAuthorize_getDataBrokerSessionOrServiceAccount(t *testing.T) {
t.Parallel()

View file

@ -16,6 +16,7 @@ type evaluatorConfig struct {
GoogleCloudServerlessAuthenticationServiceAccount string
JWTClaimsHeaders config.JWTClaimHeaders
JWTGroupsFilter config.JWTGroupsFilter
DefaultJWTIssuerFormat config.JWTIssuerFormat
}
// cacheKey() returns a hash over the configuration, except for the policies.
@ -105,3 +106,10 @@ func WithJWTGroupsFilter(groups config.JWTGroupsFilter) Option {
cfg.JWTGroupsFilter = groups
}
}
// WithDefaultJWTIssuerFormat sets the default JWT issuer format in the config.
func WithDefaultJWTIssuerFormat(format config.JWTIssuerFormat) Option {
return func(cfg *evaluatorConfig) {
cfg.DefaultJWTIssuerFormat = format
}
}

View file

@ -4,25 +4,30 @@ package evaluator
import (
"context"
"encoding/base64"
"encoding/pem"
"fmt"
"net/http"
"net/url"
"strings"
"time"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/go-jose/go-jose/v3"
"github.com/hashicorp/go-set/v3"
"github.com/open-policy-agent/opa/rego"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/authorize/checkrequest"
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/errgrouputil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/contextutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/policy/criteria"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Request contains the inputs needed for evaluation.
@ -36,30 +41,37 @@ type Request struct {
// RequestHTTP is the HTTP field in the request.
type RequestHTTP struct {
Method string `json:"method"`
Host string `json:"host"`
Hostname string `json:"hostname"`
Path string `json:"path"`
RawPath string `json:"raw_path"`
RawQuery string `json:"raw_query"`
URL string `json:"url"`
Headers map[string]string `json:"headers"`
ClientCertificate ClientCertificateInfo `json:"client_certificate"`
IP string `json:"ip"`
}
// NewRequestHTTP creates a new RequestHTTP.
func NewRequestHTTP(
method string,
requestURL url.URL,
headers map[string]string,
clientCertificate ClientCertificateInfo,
ip string,
// RequestHTTPFromCheckRequest populates a RequestHTTP from an Envoy CheckRequest proto.
func RequestHTTPFromCheckRequest(
ctx context.Context,
in *envoy_service_auth_v3.CheckRequest,
) RequestHTTP {
requestURL := checkrequest.GetURL(in)
rawPath, rawQuery, _ := strings.Cut(in.GetAttributes().GetRequest().GetHttp().GetPath(), "?")
attrs := in.GetAttributes()
clientCertMetadata := attrs.GetMetadataContext().GetFilterMetadata()["com.pomerium.client-certificate-info"]
return RequestHTTP{
Method: method,
Method: attrs.GetRequest().GetHttp().GetMethod(),
Host: attrs.GetRequest().GetHttp().GetHost(),
Hostname: requestURL.Hostname(),
Path: requestURL.Path,
RawPath: rawPath,
RawQuery: rawQuery,
URL: requestURL.String(),
Headers: headers,
ClientCertificate: clientCertificate,
IP: ip,
Headers: checkrequest.GetHeaders(in),
ClientCertificate: getClientCertificateInfo(ctx, clientCertMetadata),
IP: attrs.GetSource().GetAddress().GetSocketAddress().GetAddress(),
}
}
@ -77,6 +89,41 @@ type ClientCertificateInfo struct {
Intermediates string `json:"intermediates,omitempty"`
}
// getClientCertificateInfo translates from the client certificate Envoy
// metadata to the ClientCertificateInfo type.
func getClientCertificateInfo(
ctx context.Context, metadata *structpb.Struct,
) ClientCertificateInfo {
var c ClientCertificateInfo
if metadata == nil {
return c
}
c.Presented = metadata.Fields["presented"].GetBoolValue()
escapedChain := metadata.Fields["chain"].GetStringValue()
if escapedChain == "" {
// No validated client certificate.
return c
}
chain, err := url.QueryUnescape(escapedChain)
if err != nil {
log.Ctx(ctx).Error().Str("chain", escapedChain).Err(err).
Msg(`received unexpected client certificate "chain" value`)
return c
}
// Split the chain into the leaf and any intermediate certificates.
p, rest := pem.Decode([]byte(chain))
if p == nil {
log.Ctx(ctx).Error().Str("chain", escapedChain).
Msg(`received unexpected client certificate "chain" value (no PEM block found)`)
return c
}
c.Leaf = string(pem.EncodeToMemory(p))
c.Intermediates = string(rest)
return c
}
// RequestSession is the session field in the request.
type RequestSession struct {
ID string `json:"id"`
@ -109,7 +156,7 @@ func New(
) (*Evaluator, error) {
cfg := getConfig(options...)
err := updateStore(ctx, store, cfg)
err := updateStore(store, cfg)
if err != nil {
return nil, err
}
@ -245,6 +292,7 @@ var internalPathsNeedingLogin = set.From([]string{
"/.pomerium/webauthn",
"/.pomerium/routes",
"/.pomerium/api/v1/routes",
"/.pomerium/mcp/authorize",
})
func (e *Evaluator) evaluateInternal(_ context.Context, req *Request) (*PolicyResponse, error) {
@ -321,8 +369,8 @@ func (e *Evaluator) getClientCA(policy *config.Policy) (string, error) {
return string(e.clientCA), nil
}
func updateStore(ctx context.Context, store *store.Store, cfg *evaluatorConfig) error {
jwk, err := getJWK(ctx, cfg)
func updateStore(store *store.Store, cfg *evaluatorConfig) error {
jwk, err := getJWK(cfg)
if err != nil {
return fmt.Errorf("authorize: couldn't create signer: %w", err)
}
@ -332,13 +380,14 @@ func updateStore(ctx context.Context, store *store.Store, cfg *evaluatorConfig)
)
store.UpdateJWTClaimHeaders(cfg.JWTClaimsHeaders)
store.UpdateJWTGroupsFilter(cfg.JWTGroupsFilter)
store.UpdateDefaultJWTIssuerFormat(cfg.DefaultJWTIssuerFormat)
store.UpdateRoutePolicies(cfg.Policies)
store.UpdateSigningKey(jwk)
return nil
}
func getJWK(ctx context.Context, cfg *evaluatorConfig) (*jose.JSONWebKey, error) {
func getJWK(cfg *evaluatorConfig) (*jose.JSONWebKey, error) {
var decodedCert []byte
// if we don't have a signing key, generate one
if len(cfg.SigningKey) == 0 {
@ -358,10 +407,6 @@ func getJWK(ctx context.Context, cfg *evaluatorConfig) (*jose.JSONWebKey, error)
if err != nil {
return nil, fmt.Errorf("couldn't generate signing key: %w", err)
}
log.Ctx(ctx).Info().Str("Algorithm", jwk.Algorithm).
Str("KeyID", jwk.KeyID).
Interface("Public Key", jwk.Public()).
Msg("authorize: signing key")
return jwk, nil
}

View file

@ -10,10 +10,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpc/user"
@ -22,6 +24,113 @@ import (
"github.com/pomerium/pomerium/pkg/storage"
)
func Test_getClientCertificateInfo(t *testing.T) {
const leafPEM = `-----BEGIN CERTIFICATE-----
MIIBZTCCAQugAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPSW50ZXJt
ZWRpYXRlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMB8x
HTAbBgNVBAMTFENsaWVudCBjZXJ0aWZpY2F0ZSAxMFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAESly1cwEbcxaJBl6qAhrX1k7vejTFNE2dEbrTMpUYMl86GEWdsDYN
KSa/1wZCowPy82gPGjfAU90odkqJOusCQqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwHwYDVR0jBBgwFoAU6Qb7nEl2XHKpf/QLL6PENsHFqbowCgYIKoZIzj0EAwID
SAAwRQIgXREMUz81pYwJCMLGcV0ApaXIUap1V5n1N4VhyAGxGLYCIQC8p/LwoSgu
71H3/nCi5MxsECsvVtsmHIfwXt0wulQ1TA==
-----END CERTIFICATE-----
`
const intermediatePEM = `-----BEGIN CERTIFICATE-----
MIIBYzCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHUm9vdCBD
QTAiGA8wMDAxMDEwMTAwMDAwMFoYDzAwMDEwMTAxMDAwMDAwWjAaMRgwFgYDVQQD
Ew9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATYaTr9
uH4LpEp541/2SlKrdQZwNns+NHY/ftm++NhMDUn+izzNbPZ5aPT6VBs4Q6vbgfkK
kDaBpaKzb+uOT+o1o0IwQDAdBgNVHQ4EFgQU6Qb7nEl2XHKpf/QLL6PENsHFqbow
HwYDVR0jBBgwFoAUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0EAwIDSQAw
RgIhAMvdURs28uib2QwSMnqJjKasMb30yrSJvTiSU+lcg97/AiEA+6GpioM0c221
n/XNKVYEkPmeXHRoz9ZuVDnSfXKJoHE=
-----END CERTIFICATE-----
`
const rootPEM = `-----BEGIN CERTIFICATE-----
MIIBNzCB36ADAgECAgIQADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdSb290IENB
MCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBIxEDAOBgNVBAMT
B1Jvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS6q0mTvm29xasq7Lwk
aRGb2S/LkQFsAwaCXohSNvonCQHRMCRvA1IrQGk/oyBS5qrDoD9/7xkcVYHuTv5D
CbtuoyEwHzAdBgNVHQ4EFgQUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0E
AwIDRwAwRAIgF1ux0ridbN+bo0E3TTcNY8Xfva7yquYRMmEkfbGvSb0CIDqK80B+
fYCZHo3CID0gRSemaQ/jYMgyeBFrHIr6icZh
-----END CERTIFICATE-----
`
cases := []struct {
label string
presented bool
chain string
expected ClientCertificateInfo
expectedLog string
}{
{
"not presented",
false,
"",
ClientCertificateInfo{},
"",
},
{
"presented",
true,
url.QueryEscape(leafPEM),
ClientCertificateInfo{
Presented: true,
Leaf: leafPEM,
},
"",
},
{
"presented with intermediates",
true,
url.QueryEscape(leafPEM + intermediatePEM + rootPEM),
ClientCertificateInfo{
Presented: true,
Leaf: leafPEM,
Intermediates: intermediatePEM + rootPEM,
},
"",
},
{
"invalid chain URL encoding",
false,
"invalid%URL%encoding",
ClientCertificateInfo{},
`{"chain":"invalid%URL%encoding","error":"invalid URL escape \"%UR\"","level":"error","message":"received unexpected client certificate \"chain\" value"}`,
},
{
"invalid chain PEM encoding",
true,
"not valid PEM data",
ClientCertificateInfo{
Presented: true,
},
`{"chain":"not valid PEM data","level":"error","message":"received unexpected client certificate \"chain\" value (no PEM block found)"}`,
},
}
ctx := context.Background()
for i := range cases {
c := &cases[i]
t.Run(c.label, func(t *testing.T) {
metadata := &structpb.Struct{
Fields: map[string]*structpb.Value{
"presented": structpb.NewBoolValue(c.presented),
"chain": structpb.NewStringValue(c.chain),
},
}
var info ClientCertificateInfo
logOutput := testutil.CaptureLogs(t, func() {
info = getClientCertificateInfo(ctx, metadata)
})
assert.Equal(t, c.expected, info)
assert.Contains(t, logOutput, c.expectedLog)
})
}
}
func TestEvaluator(t *testing.T) {
signingKey, err := cryptutil.NewSigningKey()
require.NoError(t, err)
@ -527,13 +636,9 @@ func TestEvaluator(t *testing.T) {
t.Run("http method", func(t *testing.T) {
res, err := eval(t, options, []proto.Message{}, &Request{
Policy: policies[8],
HTTP: NewRequestHTTP(
http.MethodGet,
*mustParseURL("https://from.example.com/"),
nil,
ClientCertificateInfo{},
"",
),
HTTP: RequestHTTP{
Method: http.MethodGet,
},
})
require.NoError(t, err)
assert.True(t, res.Allow.Value)
@ -541,13 +646,10 @@ func TestEvaluator(t *testing.T) {
t.Run("http path", func(t *testing.T) {
res, err := eval(t, options, []proto.Message{}, &Request{
Policy: policies[9],
HTTP: NewRequestHTTP(
"POST",
*mustParseURL("https://from.example.com/test"),
nil,
ClientCertificateInfo{},
"",
),
HTTP: RequestHTTP{
Method: "POST",
Path: "/test",
},
})
require.NoError(t, err)
assert.True(t, res.Allow.Value)

View file

@ -44,6 +44,8 @@ func TestGCPIdentityTokenSource(t *testing.T) {
}
func Test_normalizeServiceAccount(t *testing.T) {
t.Parallel()
tests := []struct {
name string
serviceAccount string
@ -59,7 +61,6 @@ func Test_normalizeServiceAccount(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
gotServiceAccount, err := normalizeServiceAccount(tc.serviceAccount)

View file

@ -9,7 +9,7 @@ import (
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// HeadersResponse is the output from the headers.rego script.

View file

@ -7,8 +7,8 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"reflect"
"slices"
"strings"
"time"
@ -20,6 +20,8 @@ import (
"github.com/pomerium/datasource/pkg/directory"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/headertemplate"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/session"
@ -149,20 +151,20 @@ func (e *headersEvaluatorEvaluation) fillSetRequestHeaders(ctx context.Context)
}
for k, v := range e.request.Policy.SetRequestHeaders {
e.response.Headers.Add(k, os.Expand(v, func(name string) string {
switch name {
case "$":
return "$"
case "pomerium.access_token":
e.response.Headers.Add(k, headertemplate.Render(v, func(ref []string) string {
switch {
case slices.Equal(ref, []string{"pomerium", "access_token"}):
s, _ := e.getSessionOrServiceAccount(ctx)
return s.GetOauthToken().GetAccessToken()
case "pomerium.client_cert_fingerprint":
case slices.Equal(ref, []string{"pomerium", "client_cert_fingerprint"}):
return e.getClientCertFingerprint()
case "pomerium.id_token":
case slices.Equal(ref, []string{"pomerium", "id_token"}):
s, _ := e.getSessionOrServiceAccount(ctx)
return s.GetIdToken().GetRaw()
case "pomerium.jwt":
case slices.Equal(ref, []string{"pomerium", "jwt"}):
return e.getSignedJWT(ctx)
case len(ref) > 3 && ref[0] == "pomerium" && ref[1] == "request" && ref[2] == "headers":
return e.request.HTTP.Headers[httputil.CanonicalHeaderKey(ref[3])]
}
return ""
@ -247,18 +249,16 @@ func (e *headersEvaluatorEvaluation) getGroupIDs(ctx context.Context) []string {
return make([]string, 0)
}
func (e *headersEvaluatorEvaluation) getJWTPayloadIss() (string, error) {
var issuerFormat string
if e.request.Policy != nil {
func (e *headersEvaluatorEvaluation) getJWTPayloadIss() string {
issuerFormat := e.evaluator.store.GetDefaultJWTIssuerFormat()
if e.request.Policy != nil && e.request.Policy.JWTIssuerFormat != "" {
issuerFormat = e.request.Policy.JWTIssuerFormat
}
switch issuerFormat {
case "uri":
return fmt.Sprintf("https://%s/", e.request.HTTP.Hostname), nil
case "", "hostOnly":
return e.request.HTTP.Hostname, nil
case config.JWTIssuerFormatURI:
return fmt.Sprintf("https://%s/", e.request.HTTP.Hostname)
default:
return "", fmt.Errorf("unsupported JWT issuer format: %s", issuerFormat)
return e.request.HTTP.Hostname
}
}
@ -412,14 +412,9 @@ func (e *headersEvaluatorEvaluation) getJWTPayload(ctx context.Context) (map[str
return e.cachedJWTPayload, nil
}
iss, err := e.getJWTPayloadIss()
if err != nil {
return nil, err
}
e.gotJWTPayload = true
e.cachedJWTPayload = map[string]any{
"iss": iss,
"iss": e.getJWTPayloadIss(),
"aud": e.getJWTPayloadAud(),
"jti": e.getJWTPayloadJTI(),
"iat": e.getJWTPayloadIAT(),

View file

@ -218,15 +218,19 @@ func TestHeadersEvaluator(t *testing.T) {
HTTP: RequestHTTP{
Hostname: "from.example.com",
ClientCertificate: ClientCertificateInfo{Leaf: testValidCert},
Headers: map[string]string{
"X-Incoming-Header": "INCOMING",
},
},
Policy: &config.Policy{
SetRequestHeaders: map[string]string{
"X-Custom-Header": "CUSTOM_VALUE",
"X-ID-Token": "${pomerium.id_token}",
"X-Access-Token": "${pomerium.access_token}",
"Client-Cert-Fingerprint": "${pomerium.client_cert_fingerprint}",
"Authorization": "Bearer ${pomerium.jwt}",
"Foo": "escaped $$dollar sign",
"X-Custom-Header": "CUSTOM_VALUE",
"X-ID-Token": "${pomerium.id_token}",
"X-Access-Token": "${pomerium.access_token}",
"Client-Cert-Fingerprint": "${pomerium.client_cert_fingerprint}",
"Authorization": "Bearer ${pomerium.jwt}",
"Foo": "escaped $$dollar sign",
"X-Incoming-Custom-Header": `From-Incoming ${pomerium.request.headers["X-Incoming-Header"]}`,
},
},
Session: RequestSession{ID: "s1"},
@ -239,6 +243,7 @@ func TestHeadersEvaluator(t *testing.T) {
assert.Equal(t, "3febe6467787e93f0a01030e0803072feaa710f724a9dc74de05cfba3d4a6d23",
output.Headers.Get("Client-Cert-Fingerprint"))
assert.Equal(t, "escaped $dollar sign", output.Headers.Get("Foo"))
assert.Equal(t, "From-Incoming INCOMING", output.Headers.Get("X-Incoming-Custom-Header"))
authHeader := output.Headers.Get("Authorization")
assert.True(t, strings.HasPrefix(authHeader, "Bearer "))
authHeader = strings.TrimPrefix(authHeader, "Bearer ")
@ -437,34 +442,59 @@ func TestHeadersEvaluator(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "u1@example.com", output.Headers.Get("X-Pomerium-Claim-Email"))
})
}
t.Run("issuer format", func(t *testing.T) {
t.Parallel()
func TestHeadersEvaluator_JWTIssuerFormat(t *testing.T) {
privateJWK, _ := newJWK(t)
for _, tc := range []struct {
format string
input string
output string
}{
{"", "example.com", "example.com"},
{"hostOnly", "host-only.example.com", "host-only.example.com"},
{"uri", "uri.example.com", "https://uri.example.com/"},
} {
store := store.New()
store.UpdateSigningKey(privateJWK)
eval := func(_ *testing.T, input *Request) (*HeadersResponse, error) {
ctx := context.Background()
e := NewHeadersEvaluator(store)
return e.Evaluate(ctx, input)
}
hostname := "route.example.com"
cases := []struct {
globalFormat config.JWTIssuerFormat
routeFormat config.JWTIssuerFormat
expected string
}{
{"", "", "route.example.com"},
{"hostOnly", "", "route.example.com"},
{"uri", "", "https://route.example.com/"},
{"", "hostOnly", "route.example.com"},
{"hostOnly", "hostOnly", "route.example.com"},
{"uri", "hostOnly", "route.example.com"},
{"", "uri", "https://route.example.com/"},
{"hostOnly", "uri", "https://route.example.com/"},
{"uri", "uri", "https://route.example.com/"},
}
for _, tc := range cases {
t.Run("", func(t *testing.T) {
store.UpdateDefaultJWTIssuerFormat(tc.globalFormat)
output, err := eval(t,
nil,
&Request{
HTTP: RequestHTTP{
Hostname: tc.input,
Hostname: hostname,
},
Policy: &config.Policy{
JWTIssuerFormat: tc.format,
JWTIssuerFormat: tc.routeFormat,
},
})
require.NoError(t, err)
m := decodeJWTAssertion(t, output.Headers)
assert.Equal(t, tc.output, m["iss"], "unexpected issuer for format=%s", tc.format)
}
})
assert.Equal(t, tc.expected, m["iss"],
"unexpected issuer for global format=%q, route format=%q",
tc.globalFormat, tc.routeFormat)
})
}
}
func TestHeadersEvaluator_JWTGroupsFilter(t *testing.T) {

View file

@ -11,11 +11,11 @@ import (
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/contextutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/policy"
"github.com/pomerium/pomerium/pkg/policy/criteria"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// PolicyRequest is the input to policy evaluation.

View file

@ -2,25 +2,23 @@ package authorize
import (
"context"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/authorize/checkrequest"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/envoyconfig"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/contextutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpc/user"
@ -33,11 +31,7 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
ctx, span := a.tracer.Start(ctx, "authorize.grpc.Check")
defer span.End()
querier := storage.NewCachingQuerier(
storage.NewQuerier(a.state.Load().dataBrokerClient),
a.globalCache,
)
ctx = storage.WithQuerier(ctx, querier)
ctx = a.withQuerierForCheckRequest(ctx)
state := a.state.Load()
@ -54,8 +48,11 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
// load the session
s, err := a.loadSession(ctx, hreq, req)
if err != nil {
return nil, err
if errors.Is(err, sessions.ErrInvalidSession) {
// ENG-2172: if this is an invalid session, don't evaluate policy, return forbidden
return a.deniedResponse(ctx, in, int32(http.StatusForbidden), http.StatusText(http.StatusForbidden), nil)
} else if err != nil {
return nil, fmt.Errorf("error loading session: %w", err)
}
// if there's a session or service account, load the user
@ -80,7 +77,7 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
if err != nil {
log.Ctx(ctx).Error().Err(err).Str("request-id", requestID).Msg("grpc check ext_authz_error")
}
a.logAuthorizeCheck(ctx, in, res, s, u)
a.logAuthorizeCheck(ctx, req, res, s, u)
return resp, err
}
@ -94,7 +91,7 @@ func (a *Authorize) loadSession(
// attempt to create a session from an incoming idp token
s, err = config.NewIncomingIDPTokenSessionCreator(
func(ctx context.Context, recordType, recordID string) (*databroker.Record, error) {
return getDataBrokerRecord(ctx, recordType, recordID, 0)
return storage.GetDataBrokerRecord(ctx, recordType, recordID, 0)
},
func(ctx context.Context, records []*databroker.Record) error {
_, err := a.state.Load().dataBrokerClient.Put(ctx, &databroker.PutRequest{
@ -103,15 +100,7 @@ func (a *Authorize) loadSession(
if err != nil {
return err
}
// invalidate cache
for _, record := range records {
q := &databroker.QueryRequest{
Type: record.GetType(),
Limit: 1,
}
q.SetFilterByIDOrIndex(record.GetId())
storage.GetQuerier(ctx).InvalidateCache(ctx, q)
}
storage.InvalidateCacheForDataBrokerRecords(ctx, records...)
return nil
},
).CreateSession(ctx, a.currentConfig.Load(), req.Policy, hreq)
@ -122,6 +111,7 @@ func (a *Authorize) loadSession(
Str("request-id", requestID).
Err(err).
Msg("error creating session for incoming idp token")
return nil, err
}
sessionState, _ := a.state.Load().sessionStore.LoadSessionStateAndCheckIDP(hreq)
@ -145,18 +135,10 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(
ctx context.Context,
in *envoy_service_auth_v3.CheckRequest,
) (*evaluator.Request, error) {
requestURL := getCheckRequestURL(in)
attrs := in.GetAttributes()
clientCertMetadata := attrs.GetMetadataContext().GetFilterMetadata()["com.pomerium.client-certificate-info"]
req := &evaluator.Request{
IsInternal: envoyconfig.ExtAuthzContextExtensionsIsInternal(attrs.GetContextExtensions()),
HTTP: evaluator.NewRequestHTTP(
attrs.GetRequest().GetHttp().GetMethod(),
requestURL,
getCheckRequestHeaders(in),
getClientCertificateInfo(ctx, clientCertMetadata),
attrs.GetSource().GetAddress().GetSocketAddress().GetAddress(),
),
HTTP: evaluator.RequestHTTPFromCheckRequest(ctx, in),
}
req.Policy = a.getMatchingPolicy(envoyconfig.ExtAuthzContextExtensionsRouteID(attrs.GetContextExtensions()))
return req, nil
@ -175,9 +157,24 @@ func (a *Authorize) getMatchingPolicy(routeID uint64) *config.Policy {
return nil
}
func (a *Authorize) withQuerierForCheckRequest(ctx context.Context) context.Context {
state := a.state.Load()
q := storage.NewQuerier(state.dataBrokerClient)
// if sync queriers are enabled, use those
if len(state.syncQueriers) > 0 {
m := map[string]storage.Querier{}
for recordType, sq := range state.syncQueriers {
m[recordType] = storage.NewFallbackQuerier(sq, q)
}
q = storage.NewTypedQuerier(q, m)
}
q = storage.NewCachingQuerier(q, storage.GlobalCache)
return storage.WithQuerier(ctx, q)
}
func getHTTPRequestFromCheckRequest(req *envoy_service_auth_v3.CheckRequest) *http.Request {
hattrs := req.GetAttributes().GetRequest().GetHttp()
u := getCheckRequestURL(req)
u := checkrequest.GetURL(req)
hreq := &http.Request{
Method: hattrs.GetMethod(),
URL: &u,
@ -200,57 +197,3 @@ func getCheckRequestHeaders(req *envoy_service_auth_v3.CheckRequest) map[string]
}
return hdrs
}
func getCheckRequestURL(req *envoy_service_auth_v3.CheckRequest) url.URL {
h := req.GetAttributes().GetRequest().GetHttp()
u := url.URL{
Scheme: h.GetScheme(),
Host: h.GetHost(),
}
u.Host = urlutil.GetDomainsForURL(&u, false)[0]
// envoy sends the query string as part of the path
path := h.GetPath()
if idx := strings.Index(path, "?"); idx != -1 {
u.RawPath, u.RawQuery = path[:idx], path[idx+1:]
u.RawQuery = u.Query().Encode()
} else {
u.RawPath = path
}
u.Path, _ = url.PathUnescape(u.RawPath)
return u
}
// getClientCertificateInfo translates from the client certificate Envoy
// metadata to the ClientCertificateInfo type.
func getClientCertificateInfo(
ctx context.Context, metadata *structpb.Struct,
) evaluator.ClientCertificateInfo {
var c evaluator.ClientCertificateInfo
if metadata == nil {
return c
}
c.Presented = metadata.Fields["presented"].GetBoolValue()
escapedChain := metadata.Fields["chain"].GetStringValue()
if escapedChain == "" {
// No validated client certificate.
return c
}
chain, err := url.QueryUnescape(escapedChain)
if err != nil {
log.Ctx(ctx).Error().Str("chain", escapedChain).Err(err).
Msg(`received unexpected client certificate "chain" value`)
return c
}
// Split the chain into the leaf and any intermediate certificates.
p, rest := pem.Decode([]byte(chain))
if p == nil {
log.Ctx(ctx).Error().Str("chain", escapedChain).
Msg(`received unexpected client certificate "chain" value (no PEM block found)`)
return c
}
c.Leaf = string(pem.EncodeToMemory(p))
c.Intermediates = string(rest)
return c
}

View file

@ -18,7 +18,6 @@ import (
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
)
@ -92,20 +91,25 @@ func Test_getEvaluatorRequest(t *testing.T) {
require.NoError(t, err)
expect := &evaluator.Request{
Policy: &a.currentConfig.Load().Options.Policies[0],
HTTP: evaluator.NewRequestHTTP(
http.MethodGet,
mustParseURL("http://example.com/some/path?qs=1"),
map[string]string{
HTTP: evaluator.RequestHTTP{
Method: http.MethodGet,
Host: "example.com",
Hostname: "example.com",
Path: "/some/path",
RawPath: "/some/path",
RawQuery: "qs=1",
URL: "http://example.com/some/path?qs=1",
Headers: map[string]string{
"Accept": "text/html",
"X-Forwarded-Proto": "https",
},
evaluator.ClientCertificateInfo{
ClientCertificate: evaluator.ClientCertificateInfo{
Presented: true,
Leaf: certPEM[1:] + "\n",
Intermediates: "",
},
"",
),
IP: "",
},
}
assert.Equal(t, expect, actual)
}
@ -145,127 +149,25 @@ func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
expect := &evaluator.Request{
Policy: &a.currentConfig.Load().Options.Policies[0],
Session: evaluator.RequestSession{},
HTTP: evaluator.NewRequestHTTP(
http.MethodGet,
mustParseURL("http://example.com/some/path?qs=1"),
map[string]string{
HTTP: evaluator.RequestHTTP{
Method: http.MethodGet,
Host: "example.com:80",
Hostname: "example.com",
Path: "/some/path",
RawPath: "/some/path",
RawQuery: "qs=1",
URL: "http://example.com/some/path?qs=1",
Headers: map[string]string{
"Accept": "text/html",
"X-Forwarded-Proto": "https",
},
evaluator.ClientCertificateInfo{},
"",
),
ClientCertificate: evaluator.ClientCertificateInfo{},
IP: "",
},
}
assert.Equal(t, expect, actual)
}
func Test_getClientCertificateInfo(t *testing.T) {
const leafPEM = `-----BEGIN CERTIFICATE-----
MIIBZTCCAQugAwIBAgICEAEwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMPSW50ZXJt
ZWRpYXRlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMB8x
HTAbBgNVBAMTFENsaWVudCBjZXJ0aWZpY2F0ZSAxMFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAESly1cwEbcxaJBl6qAhrX1k7vejTFNE2dEbrTMpUYMl86GEWdsDYN
KSa/1wZCowPy82gPGjfAU90odkqJOusCQqM4MDYwEwYDVR0lBAwwCgYIKwYBBQUH
AwIwHwYDVR0jBBgwFoAU6Qb7nEl2XHKpf/QLL6PENsHFqbowCgYIKoZIzj0EAwID
SAAwRQIgXREMUz81pYwJCMLGcV0ApaXIUap1V5n1N4VhyAGxGLYCIQC8p/LwoSgu
71H3/nCi5MxsECsvVtsmHIfwXt0wulQ1TA==
-----END CERTIFICATE-----
`
const intermediatePEM = `-----BEGIN CERTIFICATE-----
MIIBYzCCAQigAwIBAgICEAEwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHUm9vdCBD
QTAiGA8wMDAxMDEwMTAwMDAwMFoYDzAwMDEwMTAxMDAwMDAwWjAaMRgwFgYDVQQD
Ew9JbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATYaTr9
uH4LpEp541/2SlKrdQZwNns+NHY/ftm++NhMDUn+izzNbPZ5aPT6VBs4Q6vbgfkK
kDaBpaKzb+uOT+o1o0IwQDAdBgNVHQ4EFgQU6Qb7nEl2XHKpf/QLL6PENsHFqbow
HwYDVR0jBBgwFoAUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0EAwIDSQAw
RgIhAMvdURs28uib2QwSMnqJjKasMb30yrSJvTiSU+lcg97/AiEA+6GpioM0c221
n/XNKVYEkPmeXHRoz9ZuVDnSfXKJoHE=
-----END CERTIFICATE-----
`
const rootPEM = `-----BEGIN CERTIFICATE-----
MIIBNzCB36ADAgECAgIQADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdSb290IENB
MCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBIxEDAOBgNVBAMT
B1Jvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS6q0mTvm29xasq7Lwk
aRGb2S/LkQFsAwaCXohSNvonCQHRMCRvA1IrQGk/oyBS5qrDoD9/7xkcVYHuTv5D
CbtuoyEwHzAdBgNVHQ4EFgQUiQ3r61y+vxDn6PMWZrpISr67HiQwCgYIKoZIzj0E
AwIDRwAwRAIgF1ux0ridbN+bo0E3TTcNY8Xfva7yquYRMmEkfbGvSb0CIDqK80B+
fYCZHo3CID0gRSemaQ/jYMgyeBFrHIr6icZh
-----END CERTIFICATE-----
`
cases := []struct {
label string
presented bool
chain string
expected evaluator.ClientCertificateInfo
expectedLog string
}{
{
"not presented",
false,
"",
evaluator.ClientCertificateInfo{},
"",
},
{
"presented",
true,
url.QueryEscape(leafPEM),
evaluator.ClientCertificateInfo{
Presented: true,
Leaf: leafPEM,
},
"",
},
{
"presented with intermediates",
true,
url.QueryEscape(leafPEM + intermediatePEM + rootPEM),
evaluator.ClientCertificateInfo{
Presented: true,
Leaf: leafPEM,
Intermediates: intermediatePEM + rootPEM,
},
"",
},
{
"invalid chain URL encoding",
false,
"invalid%URL%encoding",
evaluator.ClientCertificateInfo{},
`{"chain":"invalid%URL%encoding","error":"invalid URL escape \"%UR\"","level":"error","message":"received unexpected client certificate \"chain\" value"}`,
},
{
"invalid chain PEM encoding",
true,
"not valid PEM data",
evaluator.ClientCertificateInfo{
Presented: true,
},
`{"chain":"not valid PEM data","level":"error","message":"received unexpected client certificate \"chain\" value (no PEM block found)"}`,
},
}
ctx := context.Background()
for i := range cases {
c := &cases[i]
t.Run(c.label, func(t *testing.T) {
metadata := &structpb.Struct{
Fields: map[string]*structpb.Value{
"presented": structpb.NewBoolValue(c.presented),
"chain": structpb.NewStringValue(c.chain),
},
}
var info evaluator.ClientCertificateInfo
logOutput := testutil.CaptureLogs(t, func() {
info = getClientCertificateInfo(ctx, metadata)
})
assert.Equal(t, c.expected, info)
assert.Contains(t, logOutput, c.expectedLog)
})
}
}
type mockDataBrokerServiceClient struct {
databroker.DataBrokerServiceClient

View file

@ -21,9 +21,9 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// A Store stores data for the OPA rego policy evaluation.
@ -33,6 +33,7 @@ type Store struct {
googleCloudServerlessAuthenticationServiceAccount atomic.Pointer[string]
jwtClaimHeaders atomic.Pointer[map[string]string]
jwtGroupsFilter atomic.Pointer[config.JWTGroupsFilter]
defaultJWTIssuerFormat atomic.Pointer[config.JWTIssuerFormat]
signingKey atomic.Pointer[jose.JSONWebKey]
}
@ -66,6 +67,13 @@ func (s *Store) GetJWTGroupsFilter() config.JWTGroupsFilter {
return config.JWTGroupsFilter{}
}
func (s *Store) GetDefaultJWTIssuerFormat() config.JWTIssuerFormat {
if f := s.defaultJWTIssuerFormat.Load(); f != nil {
return *f
}
return ""
}
func (s *Store) GetSigningKey() *jose.JSONWebKey {
return s.signingKey.Load()
}
@ -89,6 +97,12 @@ func (s *Store) UpdateJWTGroupsFilter(groups config.JWTGroupsFilter) {
s.jwtGroupsFilter.Store(&groups)
}
// UpdateDefaultJWTIssuerFormat updates the JWT groups filter in the store.
func (s *Store) UpdateDefaultJWTIssuerFormat(format config.JWTIssuerFormat) {
// This isn't used by the Rego code, so we don't need to write it to the opastorage.Store instance.
s.defaultJWTIssuerFormat.Store(&format)
}
// UpdateRoutePolicies updates the route policies in the store.
func (s *Store) UpdateRoutePolicies(routePolicies []*config.Policy) {
s.write("/route_policies", routePolicies)

View file

@ -2,9 +2,7 @@ package authorize
import (
"context"
"strings"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
@ -21,19 +19,19 @@ import (
func (a *Authorize) logAuthorizeCheck(
ctx context.Context,
in *envoy_service_auth_v3.CheckRequest,
req *evaluator.Request,
res *evaluator.Result, s sessionOrServiceAccount, u *user.User,
) {
ctx, span := a.tracer.Start(ctx, "authorize.grpc.LogAuthorizeCheck")
defer span.End()
hdrs := getCheckRequestHeaders(in)
hdrs := req.HTTP.Headers
impersonateDetails := a.getImpersonateDetails(ctx, s)
evt := log.Ctx(ctx).Info().Str("service", "authorize")
fields := a.currentConfig.Load().Options.GetAuthorizeLogFields()
for _, field := range fields {
evt = populateLogEvent(ctx, field, evt, in, s, u, hdrs, impersonateDetails, res)
evt = populateLogEvent(ctx, field, evt, req, s, u, impersonateDetails, res)
}
evt = log.HTTPHeaders(evt, fields, hdrs)
@ -134,22 +132,19 @@ func populateLogEvent(
ctx context.Context,
field log.AuthorizeLogField,
evt *zerolog.Event,
in *envoy_service_auth_v3.CheckRequest,
req *evaluator.Request,
s sessionOrServiceAccount,
u *user.User,
hdrs map[string]string,
impersonateDetails *impersonateDetails,
res *evaluator.Result,
) *zerolog.Event {
path, query, _ := strings.Cut(in.GetAttributes().GetRequest().GetHttp().GetPath(), "?")
switch field {
case log.AuthorizeLogFieldCheckRequestID:
return evt.Str(string(field), hdrs["X-Request-Id"])
return evt.Str(string(field), req.HTTP.Headers["X-Request-Id"])
case log.AuthorizeLogFieldEmail:
return evt.Str(string(field), u.GetEmail())
case log.AuthorizeLogFieldHost:
return evt.Str(string(field), in.GetAttributes().GetRequest().GetHttp().GetHost())
return evt.Str(string(field), req.HTTP.Host)
case log.AuthorizeLogFieldIDToken:
if s, ok := s.(*session.Session); ok {
evt = evt.Str(string(field), s.GetIdToken().GetRaw())
@ -180,13 +175,13 @@ func populateLogEvent(
}
return evt
case log.AuthorizeLogFieldIP:
return evt.Str(string(field), in.GetAttributes().GetSource().GetAddress().GetSocketAddress().GetAddress())
return evt.Str(string(field), req.HTTP.IP)
case log.AuthorizeLogFieldMethod:
return evt.Str(string(field), in.GetAttributes().GetRequest().GetHttp().GetMethod())
return evt.Str(string(field), req.HTTP.Method)
case log.AuthorizeLogFieldPath:
return evt.Str(string(field), path)
return evt.Str(string(field), req.HTTP.RawPath)
case log.AuthorizeLogFieldQuery:
return evt.Str(string(field), query)
return evt.Str(string(field), req.HTTP.RawQuery)
case log.AuthorizeLogFieldRequestID:
return evt.Str(string(field), requestid.FromContext(ctx))
case log.AuthorizeLogFieldServiceAccountID:

View file

@ -6,8 +6,6 @@ import (
"strings"
"testing"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
@ -24,27 +22,16 @@ func Test_populateLogEvent(t *testing.T) {
ctx := context.Background()
ctx = requestid.WithValue(ctx, "REQUEST-ID")
checkRequest := &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Host: "HOST",
Path: "https://www.example.com/some/path?a=b",
Method: "GET",
},
},
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Address: &envoy_config_core_v3.Address{
Address: &envoy_config_core_v3.Address_SocketAddress{
SocketAddress: &envoy_config_core_v3.SocketAddress{
Address: "127.0.0.1",
},
},
},
},
req := &evaluator.Request{
HTTP: evaluator.RequestHTTP{
Method: "GET",
Host: "HOST",
RawPath: "/some/path",
RawQuery: "a=b",
Headers: map[string]string{"X-Request-Id": "CHECK-REQUEST-ID"},
IP: "127.0.0.1",
},
}
headers := map[string]string{"X-Request-Id": "CHECK-REQUEST-ID"}
s := &session.Session{
Id: "SESSION-ID",
IdToken: &session.IDToken{
@ -86,7 +73,7 @@ func Test_populateLogEvent(t *testing.T) {
{log.AuthorizeLogFieldImpersonateUserID, s, `{"impersonate-user-id":"IMPERSONATE-USER-ID"}`},
{log.AuthorizeLogFieldIP, s, `{"ip":"127.0.0.1"}`},
{log.AuthorizeLogFieldMethod, s, `{"method":"GET"}`},
{log.AuthorizeLogFieldPath, s, `{"path":"https://www.example.com/some/path"}`},
{log.AuthorizeLogFieldPath, s, `{"path":"/some/path"}`},
{log.AuthorizeLogFieldQuery, s, `{"query":"a=b"}`},
{log.AuthorizeLogFieldRemovedGroupsCount, s, `{"removed-groups-count":42}`},
{log.AuthorizeLogFieldRequestID, s, `{"request-id":"REQUEST-ID"}`},
@ -96,15 +83,13 @@ func Test_populateLogEvent(t *testing.T) {
{log.AuthorizeLogFieldUser, sa, `{"user":"SERVICE-ACCOUNT-USER-ID"}`},
{log.AuthorizeLogFieldUser, nil, `{"user":""}`},
} {
tc := tc
t.Run(string(tc.field), func(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
log := zerolog.New(&buf)
evt := log.Log()
evt = populateLogEvent(ctx, tc.field, evt, checkRequest, tc.s, u, headers, impersonateDetails, res)
evt = populateLogEvent(ctx, tc.field, evt, req, tc.s, u, impersonateDetails, res)
evt.Send()
assert.Equal(t, tc.expect, strings.TrimSpace(buf.String()))

View file

@ -9,18 +9,23 @@ import (
oteltrace "go.opentelemetry.io/otel/trace"
googlegrpc "google.golang.org/grpc"
"github.com/pomerium/datasource/pkg/directory"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/authorize/internal/store"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/authenticateflow"
"github.com/pomerium/pomerium/pkg/grpc"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/storage"
)
var outboundGRPCConnection = new(grpc.CachedOutboundGRPClientConn)
type authenticateFlow interface {
AuthenticateSignInURL(ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string) (string, error)
AuthenticateSignInURL(ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string, additionalLoginHosts []string) (string, error)
}
type authorizeState struct {
@ -30,14 +35,15 @@ type authorizeState struct {
dataBrokerClient databroker.DataBrokerServiceClient
sessionStore *config.SessionStore
authenticateFlow authenticateFlow
syncQueriers map[string]storage.Querier
}
func newAuthorizeStateFromConfig(
ctx context.Context,
previousState *authorizeState,
tracerProvider oteltrace.TracerProvider,
cfg *config.Config,
store *store.Store,
previousPolicyEvaluator *evaluator.Evaluator,
) (*authorizeState, error) {
if err := validateOptions(cfg.Options); err != nil {
return nil, fmt.Errorf("authorize: bad options: %w", err)
@ -46,8 +52,12 @@ func newAuthorizeStateFromConfig(
state := new(authorizeState)
var err error
var previousEvaluator *evaluator.Evaluator
if previousState != nil {
previousEvaluator = previousState.evaluator
}
state.evaluator, err = newPolicyEvaluator(ctx, cfg.Options, store, previousPolicyEvaluator)
state.evaluator, err = newPolicyEvaluator(ctx, cfg.Options, store, previousEvaluator)
if err != nil {
return nil, fmt.Errorf("authorize: failed to update policy with options: %w", err)
}
@ -88,5 +98,29 @@ func newAuthorizeStateFromConfig(
return nil, err
}
state.syncQueriers = make(map[string]storage.Querier)
if previousState != nil {
if previousState.dataBrokerClientConnection == state.dataBrokerClientConnection {
state.syncQueriers = previousState.syncQueriers
} else {
for _, v := range previousState.syncQueriers {
v.Stop()
}
}
}
if cfg.Options.IsRuntimeFlagSet(config.RuntimeFlagAuthorizeUseSyncedData) {
for _, recordType := range []string{
grpcutil.GetTypeURL(new(session.Session)),
grpcutil.GetTypeURL(new(user.User)),
grpcutil.GetTypeURL(new(user.ServiceAccount)),
directory.GroupRecordType,
directory.UserRecordType,
} {
if _, ok := state.syncQueriers[recordType]; !ok {
state.syncQueriers[recordType] = storage.NewSyncQuerier(state.dataBrokerClient, recordType)
}
}
}
return state, nil
}

111
changelogs/v0.29.0.md Normal file
View file

@ -0,0 +1,111 @@
## What's Changed
### Breaking
- Enable seamless request tracing across multiple services with the new OTEL-based tracing system. Users can now easily configure and understand traces, with improved visibility into the flow of requests, even at low sample rates. All previously supported tracing methods are removed. ([@kralicky](https://github.com/kralicky) in [#5388](https://github.com/pomerium/pomerium/pull/5388)) and [#5447](https://github.com/pomerium/pomerium/pull/5447))
## New
- New `jwt_issuer_format` global setting. ([@kenjenkins](https://github.com/kenjenkins) in [#5519](https://github.com/pomerium/pomerium/pull/5519))
- Enable UDP routes with `CONNECT-UDP` tunneling. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5390](https://github.com/pomerium/pomerium/pull/5390))
- HTTP/3 Support ([@calebdoxsey](https://github.com/calebdoxsey) in [#5349](https://github.com/pomerium/pomerium/pull/5349))
- Enable authorization errors to return a JSON response instead of HTML, providing a cleaner and more consistent error format for developers using gRPC services. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5400](https://github.com/pomerium/pomerium/pull/5400) (ENG-1750))
- Prevent false positive vulnerability reports by only generating a fallback certificate when no other certificate is configured, minimizing unnecessary certificate generation. ([@kenjenkins](https://github.com/kenjenkins) in [#5250](https://github.com/pomerium/pomerium/pull/5250))
- Enable precise control over user group claims in JWTs by allowing filtering of groups either globally or per route. This enhancement helps reduce excessive group lists, preventing large headers that can disrupt upstream services while maintaining the integrity of signed JWTs. ([@kenjenkins](https://github.com/kenjenkins) in [#5417](https://github.com/pomerium/pomerium/pull/5417) (ENG-1802))
- Enable core Pomerium to access the original PPL policy by adding a `source_ppl` field to the configuration, ensuring better introspection and compatibility with the Routes Portal. This enhancement allows the raw PPL to be passed alongside the generated Rego, providing more comprehensive policy visibility. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5419](https://github.com/pomerium/pomerium/pull/5419) (ENG-1832))
* importutil: refactor GenerateRouteNames to allow for protobuf or config routes by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5427
- Add names, descriptions, and logos to routes, enhancing route cards with clear identifiers and visual appeal. Enjoy a more informative and engaging interface with route-specific icons and descriptions, and easily connect to services with direct links or command instructions. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5424](https://github.com/pomerium/pomerium/pull/5424) (ENG-1833))
- Enhance Directory Group query performance by introducing a cache warming feature that preloads records, significantly reducing delays and timeouts. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5439](https://github.com/pomerium/pomerium/pull/5439) (ENG-1915))
- Access your available routes through a new JSON endpoint at `/.pomerium/api/v1/routes`, providing a list tailored to your permissions. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5428](https://github.com/pomerium/pomerium/pull/5428) (ENG-1845))
- Discover available routes with a new HTML page that displays each accessible route as a card. This intuitive interface makes navigation and route management simpler and more efficient. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5443](https://github.com/pomerium/pomerium/pull/5443) (ENG-1871))
- Discover and display site logos automatically by fetching and embedding favicons from destination addresses, enhancing visual recognition and user experience. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5448](https://github.com/pomerium/pomerium/pull/5448))
- Enhance user experience with new icons for well-known services, making it easier to identify them at a glance. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5453](https://github.com/pomerium/pomerium/pull/5453))
- Enable dynamic configuration reloading by handling `SIGHUP` signals, allowing updates without restarting the application. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5459](https://github.com/pomerium/pomerium/pull/5459))
- Enable customization of the HTTP/3 advertise port in the `Alt-Svc` header, enhancing flexibility for configurations using protobuf. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5466](https://github.com/pomerium/pomerium/pull/5466))
- Authenticate using IdP access and identity tokens, with initial support for Azure AD access tokens. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5484](https://github.com/pomerium/pomerium/pull/5484) (ENG-2001, ENG-2001))
- Improve file management by setting consistent default directories and logging errors when directory environment variables are unset. Ensure file writes are atomic to prevent redundancy and potential file conflicts, enhancing reliability and reducing clutter in temporary storage. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5477](https://github.com/pomerium/pomerium/pull/5477))
- Enhance performance by reducing redundant session creation for identical IdP tokens. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5491](https://github.com/pomerium/pomerium/pull/5491) (ENG-2025, ENG-2025))
- Enhance PPL logic with new `not` and `exclude` operators, allowing more flexible string and list matching. Define more precise permissions by excluding specific domains or groups in your policy configurations. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5490](https://github.com/pomerium/pomerium/pull/5490) (ENG-2030, ENG-2030))
* Support loading idp token sessions in the proxy service by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5488
* Handle long names in the cards for route portal by @nhayfield in https://github.com/pomerium/pomerium/pull/5514
### Fixes
- Ensure the "groups" claim in JWTs is serialized as an empty list instead of JSON null, improving compatibility with third-party libraries. ([@kenjenkins](https://github.com/kenjenkins) in [#5394](https://github.com/pomerium/pomerium/pull/5394))
- Ensure complete and accurate metrics output by properly flushing the buffered writer. ([@kenjenkins](https://github.com/kenjenkins) in [#5398](https://github.com/pomerium/pomerium/pull/5398))
- Ensure custom branding settings are consistently applied across all pages, even when using multiple configuration sources. This resolves an issue where core pages were not displaying the correct branding when using an ingress controller, ensuring a uniform appearance with your chosen colors and logo. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5401](https://github.com/pomerium/pomerium/pull/5401) (ENG-1766))
- Ensure the HTTP redirect server properly uses the proxy protocol when configured. ([@calebdoxsey](https://github.com/calebdoxsey) in [#5405](https://github.com/pomerium/pomerium/pull/5405))
- Ensure that logo URLs containing `%` signs are correctly processed, preventing configuration errors in Envoy. This fix allows branding options with special characters to be used without causing issues. ([@kenjenkins](https://github.com/kenjenkins) in [#5460](https://github.com/pomerium/pomerium/pull/5460) (ENG-1958))
- Fix the `identity_manager_last_session_refresh_errors` metrics view. ([@kenjenkins](https://github.com/kenjenkins) in [#5543](https://github.com/pomerium/pomerium/pull/5543))
- Reduce memory usage during metrics output by @wasaga in https://github.com/pomerium/pomerium/pull/5530
- Ensure Pomerium in Zero mode can connect to the cloud control plane using `HTTPS_PROXY` egress proxy by @wasaga in https://github.com/pomerium/pomerium/pull/5520
### Changed
* authorize: enable WaitForReady on databroker query requests by @kralicky in https://github.com/pomerium/pomerium/pull/5415
* authorize: filter only by group ID by @kenjenkins in https://github.com/pomerium/pomerium/pull/5437
* authorize: log JWT groups filtering by @kenjenkins in https://github.com/pomerium/pomerium/pull/5432
* authorize: remove audit logging by @wasaga in https://github.com/pomerium/pomerium/pull/5369
* authorize: remove unused mutex by @wasaga in https://github.com/pomerium/pomerium/pull/5442
* authorize: remove wait for ready by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5376
* authorize: return 403 on invalid sessions by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5536
* chore(deps): bump golang.org/x/net from 0.31.0 to 0.33.0 by @kenjenkins in https://github.com/pomerium/pomerium/pull/5404
* config: add internal_address_config to address deprecation warning by @kralicky in https://github.com/pomerium/pomerium/pull/5425
* config: add new OTLP tracing fields by @kenjenkins in https://github.com/pomerium/pomerium/pull/5421
* config: add options to adjust databroker lease ttl, and retry initial interval by @kralicky in https://github.com/pomerium/pomerium/pull/5391
* config: adjust envoy otel trace batching settings to match go sdk by @kralicky in https://github.com/pomerium/pomerium/pull/5446
* config: fix JWT groups filter option by @kenjenkins in https://github.com/pomerium/pomerium/pull/5429
* config: preserve existing user when creating sessions from idp token by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5502
* config: reimplement file watcher by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5498
* config: set default tracing sample rate to 1.0 by @kralicky in https://github.com/pomerium/pomerium/pull/5422
* config: support emails from directory user by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5504
* envoy: enable extended connect by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5387
* get-envoy: allow downloading a specific os/arch by @kralicky in https://github.com/pomerium/pomerium/pull/5499
* identity: disable session refresh for idp token sessions, fix query cache invalidation by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5495
* internal: cleanup headers by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5408
* internal: fix dependencies by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5450
* internal: fix reporter client context on config change by @kralicky in https://github.com/pomerium/pomerium/pull/5392
* internal: Fix trace client update by @kralicky in https://github.com/pomerium/pomerium/pull/5480
* internal: improve port allocation by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5485
* internal: pgx client tracing by @kralicky in https://github.com/pomerium/pomerium/pull/5438
* internal: remove noisy logs by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5501
* internal: replace xxhash with xxh3 by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5457
* metrics: restore global registry in unit tests by @kenjenkins in https://github.com/pomerium/pomerium/pull/5399
* proxy: add short timeout for logo discovery by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5506
* proxy: disable HTTP2 `CONNECT` for websockets by @wasaga in https://github.com/pomerium/pomerium/pull/5516
* proxy: fix connect command in routes portal by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5475
* proxy: fix routes portal error message by @calebdoxsey in https://github.com/pomerium/pomerium/pull/5476
* proxy: minor cleanup in GenerateCatchAllCertificate by @kenjenkins in https://github.com/pomerium/pomerium/pull/5397
* proxy: only enable IPv6 addresses in the Envoy if IPv6 is enabled by @wasaga in https://github.com/pomerium/pomerium/pull/5538
* testenv: avoid dns lookups for *.localhost.pomerium.io by @kralicky in https://github.com/pomerium/pomerium/pull/5372
* testenv: embedded envoy cpu/memory profiling config by @kralicky in https://github.com/pomerium/pomerium/pull/5377
* testenv: fix testcontainers docker client using the global tracer provider by @kralicky in https://github.com/pomerium/pomerium/pull/5440
* tests: Fix small timeout causing test flake by @kralicky in https://github.com/pomerium/pomerium/pull/5436
* tests: remove test code from config/options_test.go by @kralicky in https://github.com/pomerium/pomerium/pull/5423
* tracing: add missing check for otel_exporter_otlp_endpoint in envoy trace config by @kralicky in https://github.com/pomerium/pomerium/pull/5472
* tracing: handle empty protocol by @kralicky in https://github.com/pomerium/pomerium/pull/5474
### Dependency Updates
* chore(deps): bump the github-actions group with 4 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5383
* chore(deps): bump the docker group in /.github with 3 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5382
* chore(deps): bump the docker group with 3 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5380
* chore(deps): bump github.com/quic-go/quic-go from 0.48.1 to 0.48.2 by @dependabot in https://github.com/pomerium/pomerium/pull/5384
* chore(deps): bump the go group across 1 directory with 26 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5385
* chore(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /ui by @dependabot in https://github.com/pomerium/pomerium/pull/5373
* chore(deps): bump golang.org/x/crypto from 0.29.0 to 0.31.0 by @dependabot in https://github.com/pomerium/pomerium/pull/5396
* chore(deps): bump busybox from `db142d4` to `2919d01` in /.github in the docker group by @dependabot in https://github.com/pomerium/pomerium/pull/5414
* chore(deps): bump the go group with 27 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5412
* chore(deps): bump the docker group with 2 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5413
* chore(deps): bump the docker group in /.github with 3 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5464
* chore(deps): bump the docker group with 3 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5463
* chore(deps): bump the github-actions group with 14 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5462
* upgrade x/oauth2 from v0.24.0 to v0.27.0 by @kenjenkins in https://github.com/pomerium/pomerium/pull/5493
* chore(deps): bump github.com/go-jose/go-jose/v3 from 3.0.3 to 3.0.4 by @dependabot in https://github.com/pomerium/pomerium/pull/5505
* chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.2 to 4.0.5 by @dependabot in https://github.com/pomerium/pomerium/pull/5496
* chore(deps-dev): bump esbuild from 0.21.1 to 0.25.0 in /ui by @dependabot in https://github.com/pomerium/pomerium/pull/5478
* chore(deps): bump busybox from `a5d0ce4` to `498a000` in /.github in the docker group by @dependabot in https://github.com/pomerium/pomerium/pull/5512
* chore(deps): bump the github-actions group with 7 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5510
* chore(deps): bump the go group across 1 directory with 44 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5511
* chore(deps): bump the docker group with 2 updates by @dependabot in https://github.com/pomerium/pomerium/pull/5509
## New Contributors
* @gaurdro made their first contribution in https://github.com/pomerium/pomerium/pull/5456
**Full Changelog**: https://github.com/pomerium/pomerium/compare/v0.28.0...v0.29.0

View file

@ -12,13 +12,13 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/version"
_ "github.com/pomerium/pomerium/internal/zero/bootstrap/writers/filesystem"
_ "github.com/pomerium/pomerium/internal/zero/bootstrap/writers/k8s"
zero_cmd "github.com/pomerium/pomerium/internal/zero/cmd"
"github.com/pomerium/pomerium/pkg/cmd/pomerium"
"github.com/pomerium/pomerium/pkg/envoy/files"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
func main() {
@ -53,10 +53,6 @@ func main() {
}
func run(ctx context.Context, configFile string) error {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("config_file_source", configFile).Bool("bootstrap", true)
})
var src config.Source
src, err := config.NewFileOrEnvironmentSource(ctx, configFile, files.FullVersion())

View file

@ -90,7 +90,7 @@ func TestAutocertOptions_Validate(t *testing.T) {
}
},
"ok/trusted-ca-file": func(t *testing.T) test {
f, err := os.CreateTemp("", "pomerium-test-ca")
f, err := os.CreateTemp(t.TempDir(), "pomerium-test-ca")
require.NoError(t, err)
n, err := f.Write(certPEM)
require.NoError(t, err)
@ -128,7 +128,7 @@ func TestAutocertOptions_Validate(t *testing.T) {
}
},
"fail/trusted-ca-combined": func(t *testing.T) test {
f, err := os.CreateTemp("", "pomerium-test-ca")
f, err := os.CreateTemp(t.TempDir(), "pomerium-test-ca")
require.NoError(t, err)
n, err := f.Write(certPEM)
require.NoError(t, err)

View file

@ -108,10 +108,6 @@ func NewFileOrEnvironmentSource(
ctx context.Context,
configFile, envoyVersion string,
) (*FileOrEnvironmentSource, error) {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("config_file_source", configFile)
})
options, err := newOptionsFromConfig(configFile)
if err != nil {
return nil, err
@ -167,7 +163,7 @@ func NewFileOrEnvironmentSource(
func (src *FileOrEnvironmentSource) check(ctx context.Context) {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("config_change_id", uuid.New().String())
return c.Str("config-change-id", uuid.New().String())
})
src.mu.Lock()
cfg := src.config
@ -254,7 +250,7 @@ func (src *FileWatcherSource) onConfigChange(ctx context.Context, cfg *Config) {
// store the config and trigger an update
src.cfg = cfg.Clone()
src.hash = getAllConfigFilePathsHash(src.cfg)
log.Ctx(ctx).Info().Uint64("hash", src.hash).Msg("config/filewatchersource: underlying config change, triggering update")
log.Ctx(ctx).Debug().Uint64("hash", src.hash).Msg("config/filewatchersource: underlying config change, triggering update")
src.Trigger(ctx, src.cfg)
}

View file

@ -6,10 +6,11 @@ import (
"encoding/pem"
"testing"
"github.com/pomerium/pomerium/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"github.com/pomerium/pomerium/config"
)
func TestGenerateCatchAllCertificate(t *testing.T) {

View file

@ -23,6 +23,7 @@ import (
"github.com/pomerium/pomerium/internal/hashutil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/urlutil"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/policy/parser"
)
@ -617,3 +618,64 @@ func (f JWTGroupsFilter) Equal(other JWTGroupsFilter) bool {
}
return f.set.Equal(other.set)
}
type JWTIssuerFormat string
const (
JWTIssuerFormatUnset JWTIssuerFormat = ""
JWTIssuerFormatHostOnly JWTIssuerFormat = "hostOnly"
JWTIssuerFormatURI JWTIssuerFormat = "uri"
)
var knownJWTIssuerFormats = map[JWTIssuerFormat]struct{}{
JWTIssuerFormatUnset: {},
JWTIssuerFormatHostOnly: {},
JWTIssuerFormatURI: {},
}
func JWTIssuerFormatFromPB(format *configpb.IssuerFormat) JWTIssuerFormat {
if format == nil {
return JWTIssuerFormatUnset
}
switch *format {
case configpb.IssuerFormat_IssuerHostOnly:
return JWTIssuerFormatHostOnly
case configpb.IssuerFormat_IssuerURI:
return JWTIssuerFormatURI
default:
return JWTIssuerFormatUnset
}
}
func (f JWTIssuerFormat) ToPB() *configpb.IssuerFormat {
switch f {
case JWTIssuerFormatUnset:
return nil
case JWTIssuerFormatHostOnly:
return configpb.IssuerFormat_IssuerHostOnly.Enum()
case JWTIssuerFormatURI:
return configpb.IssuerFormat_IssuerURI.Enum()
default:
return nil
}
}
func (f JWTIssuerFormat) Valid() bool {
_, ok := knownJWTIssuerFormats[f]
return ok
}
func MCPFromPB(src *configpb.MCP) *MCP {
if src == nil {
return nil
}
return &MCP{}
}
func MCPToPB(src *MCP) *configpb.MCP {
if src == nil {
return nil
}
return &configpb.MCP{}
}

View file

@ -8,7 +8,7 @@ import (
)
func TestBuilder_buildACMETLSALPNCluster(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", nil, nil)
b := New("local-grpc", "local-http", "local-metrics", nil, nil, true)
testutil.AssertProtoJSONEqual(t,
`{
"name": "pomerium-acme-tls-alpn",
@ -34,7 +34,7 @@ func TestBuilder_buildACMETLSALPNCluster(t *testing.T) {
}
func TestBuilder_buildACMETLSALPNFilterChain(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", nil, nil)
b := New("local-grpc", "local-http", "local-metrics", nil, nil, true)
testutil.AssertProtoJSONEqual(t,
`{
"filterChainMatch": {

View file

@ -16,12 +16,13 @@ import (
envoy_config_overload_v3 "github.com/envoyproxy/go-control-plane/envoy/config/overload/v3"
envoy_extensions_access_loggers_file_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3"
envoy_extensions_resource_monitors_downstream_connections_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/resource_monitors/downstream_connections/v3"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/otelconfig"
"github.com/pomerium/pomerium/internal/telemetry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
const maxActiveDownstreamConnections = 50000

View file

@ -13,7 +13,7 @@ import (
func TestBuilder_BuildBootstrapAdmin(t *testing.T) {
t.Setenv("TMPDIR", "/tmp")
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
t.Run("valid", func(t *testing.T) {
adminCfg, err := b.BuildBootstrapAdmin(&config.Config{
Options: &config.Options{
@ -35,7 +35,7 @@ func TestBuilder_BuildBootstrapAdmin(t *testing.T) {
}
func TestBuilder_BuildBootstrapLayeredRuntime(t *testing.T) {
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil, true)
staticCfg, err := b.BuildBootstrapLayeredRuntime(context.Background(), &config.Config{})
assert.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
@ -61,7 +61,7 @@ func TestBuilder_BuildBootstrapLayeredRuntime(t *testing.T) {
func TestBuilder_BuildBootstrapStaticResources(t *testing.T) {
t.Run("valid", func(t *testing.T) {
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil, true)
staticCfg, err := b.BuildBootstrapStaticResources(context.Background(), &config.Config{}, false)
assert.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
@ -105,14 +105,14 @@ func TestBuilder_BuildBootstrapStaticResources(t *testing.T) {
`, staticCfg)
})
t.Run("bad gRPC address", func(t *testing.T) {
b := New("xyz:zyx", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
b := New("xyz:zyx", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil, true)
_, err := b.BuildBootstrapStaticResources(context.Background(), &config.Config{}, false)
assert.Error(t, err)
})
}
func TestBuilder_BuildBootstrapStatsConfig(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
t.Run("valid", func(t *testing.T) {
statsCfg, err := b.BuildBootstrapStatsConfig(&config.Config{
Options: &config.Options{
@ -132,7 +132,7 @@ func TestBuilder_BuildBootstrapStatsConfig(t *testing.T) {
}
func TestBuilder_BuildBootstrap(t *testing.T) {
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil, true)
t.Run("OverloadManager", func(t *testing.T) {
bootstrap, err := b.BuildBootstrap(context.Background(), &config.Config{
Options: &config.Options{

View file

@ -7,11 +7,12 @@ import (
// A Builder builds envoy config from pomerium config.
type Builder struct {
localGRPCAddress string
localHTTPAddress string
localMetricsAddress string
filemgr *filemgr.Manager
reproxy *reproxy.Handler
localGRPCAddress string
localHTTPAddress string
localMetricsAddress string
filemgr *filemgr.Manager
reproxy *reproxy.Handler
addIPV6InternalRanges bool
}
// New creates a new Builder.
@ -21,15 +22,17 @@ func New(
localMetricsAddress string,
fileManager *filemgr.Manager,
reproxyHandler *reproxy.Handler,
addIPV6InternalRanges bool,
) *Builder {
if reproxyHandler == nil {
reproxyHandler = reproxy.New()
}
return &Builder{
localGRPCAddress: localGRPCAddress,
localHTTPAddress: localHTTPAddress,
localMetricsAddress: localMetricsAddress,
filemgr: fileManager,
reproxy: reproxyHandler,
localGRPCAddress: localGRPCAddress,
localHTTPAddress: localHTTPAddress,
localMetricsAddress: localMetricsAddress,
filemgr: fileManager,
reproxy: reproxyHandler,
addIPV6InternalRanges: addIPV6InternalRanges,
}
}

View file

@ -20,8 +20,8 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// BuildClusters builds envoy clusters from the given config.
@ -216,7 +216,6 @@ func (b *Builder) buildPolicyEndpoints(
) ([]Endpoint, error) {
var endpoints []Endpoint
for _, dst := range policy.To {
dst := dst
ts, err := b.buildPolicyTransportSocket(ctx, cfg, policy, dst.URL)
if err != nil {
return nil, err

View file

@ -27,7 +27,7 @@ func Test_BuildClusters(t *testing.T) {
opts := config.NewDefaultOptions()
ctx := context.Background()
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
clusters, err := b.BuildClusters(ctx, &config.Config{Options: opts})
require.NoError(t, err)
testutil.AssertProtoJSONFileEqual(t, "testdata/clusters.json", clusters)
@ -38,7 +38,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
cacheDir, _ := os.UserCacheDir()
customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-3133535332543131503345494c.pem")
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
rootCABytes, _ := getCombinedCertificateAuthority(ctx, &config.Config{Options: &config.Options{}})
rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
@ -517,7 +517,7 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
func Test_buildCluster(t *testing.T) {
ctx := context.Background()
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
rootCABytes, _ := getCombinedCertificateAuthority(ctx, &config.Config{Options: &config.Options{}})
rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
o1 := config.NewDefaultOptions()
@ -1012,7 +1012,7 @@ func Test_bindConfig(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*10)
defer clearTimeout()
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
t.Run("no bind config", func(t *testing.T) {
cluster, err := b.buildPolicyCluster(ctx, &config.Config{Options: &config.Options{}}, &config.Policy{
From: "https://from.example.com",

View file

@ -44,10 +44,10 @@ func ExtAuthzFilter(grpcClientTimeout *durationpb.Duration) *envoy_extensions_fi
}
// HTTPConnectionManagerFilter creates a new HTTP connection manager filter.
func HTTPConnectionManagerFilter(
func (b *Builder) HTTPConnectionManagerFilter(
httpConnectionManager *envoy_extensions_filters_network_http_connection_manager.HttpConnectionManager,
) *envoy_config_listener_v3.Filter {
applyGlobalHTTPConnectionManagerOptions(httpConnectionManager)
b.applyGlobalHTTPConnectionManagerOptions(httpConnectionManager)
return &envoy_config_listener_v3.Filter{
Name: "envoy.filters.network.http_connection_manager",
ConfigType: &envoy_config_listener_v3.Filter_TypedConfig{

View file

@ -1,6 +1,7 @@
package envoyconfig
import (
"encoding/json"
"fmt"
"strings"
@ -19,6 +20,7 @@ func (b *Builder) buildVirtualHost(
options *config.Options,
name string,
host string,
hasMCPPolicy bool,
) (*envoy_config_route_v3.VirtualHost, error) {
vh := &envoy_config_route_v3.VirtualHost{
Name: name,
@ -35,7 +37,7 @@ func (b *Builder) buildVirtualHost(
}
// these routes match /.pomerium/... and similar paths
rs, err := b.buildPomeriumHTTPRoutes(options, host)
rs, err := b.buildPomeriumHTTPRoutes(options, host, hasMCPPolicy)
if err != nil {
return nil, err
}
@ -56,6 +58,15 @@ func (b *Builder) buildLocalReplyConfig(
headers = toEnvoyHeaders(options.GetSetResponseHeaders())
}
jsonBody, err := json.MarshalIndent(map[string]any{
"requestId": "%STREAM_ID%",
"status": "%RESPONSE_CODE%",
"statusText": "%RESPONSE_CODE_DETAILS%",
}, "", " ")
if err != nil {
return nil, fmt.Errorf("error rendering error json for local reply: %w", err)
}
data := make(map[string]any)
httputil.AddBrandingOptionsToMap(data, options.BrandingOptions)
for k, v := range data {
@ -70,81 +81,146 @@ func (b *Builder) buildLocalReplyConfig(
data["requestId"] = "%STREAM_ID%"
data["responseFlags"] = "%RESPONSE_FLAGS%"
bs, err := ui.RenderPage("Error", "Error", data)
htmlBody, err := ui.RenderPage("Error", "Error", data)
if err != nil {
return nil, fmt.Errorf("error rendering error page for local reply: %w", err)
}
responseFlagFilter := &envoy_config_accesslog_v3.AccessLogFilter_ResponseFlagFilter{
ResponseFlagFilter: &envoy_config_accesslog_v3.ResponseFlagFilter{
Flags: []string{
"DC",
"DF",
"DI",
"DO",
"DPE",
"DT",
"FI",
"IH",
"LH",
"LR",
"NC",
"NFCF",
"NR",
"OM",
"RFCF",
"RL",
"RLSE",
"SI",
// "UAEX", // excluded because this response is handled in the authorize service
"UC",
"UF",
"UH",
"UMSDR",
"UO",
"UPE",
"UR",
"URX",
"UT",
},
},
}
return &envoy_http_connection_manager.LocalReplyConfig{
Mappers: []*envoy_http_connection_manager.ResponseMapper{{
Filter: &envoy_config_accesslog_v3.AccessLogFilter{
FilterSpecifier: &envoy_config_accesslog_v3.AccessLogFilter_ResponseFlagFilter{
ResponseFlagFilter: &envoy_config_accesslog_v3.ResponseFlagFilter{
Flags: []string{
"DC",
"DF",
"DI",
"DO",
"DPE",
"DT",
"FI",
"IH",
"LH",
"LR",
"NC",
"NFCF",
"NR",
"OM",
"RFCF",
"RL",
"RLSE",
"SI",
// "UAEX", // excluded because this response is handled in the authorize service
"UC",
"UF",
"UH",
"UMSDR",
"UO",
"UPE",
"UR",
"URX",
"UT",
Mappers: []*envoy_http_connection_manager.ResponseMapper{
{
Filter: &envoy_config_accesslog_v3.AccessLogFilter{
FilterSpecifier: &envoy_config_accesslog_v3.AccessLogFilter_AndFilter{
AndFilter: &envoy_config_accesslog_v3.AndFilter{
Filters: []*envoy_config_accesslog_v3.AccessLogFilter{
{FilterSpecifier: responseFlagFilter},
{FilterSpecifier: &envoy_config_accesslog_v3.AccessLogFilter_MetadataFilter{
MetadataFilter: &envoy_config_accesslog_v3.MetadataFilter{
Matcher: buildLocalReplyTypeMatcher("plain"),
},
}},
},
},
},
},
},
BodyFormatOverride: &envoy_config_core_v3.SubstitutionFormatString{
ContentType: "text/html; charset=UTF-8",
Format: &envoy_config_core_v3.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: bs,
BodyFormatOverride: &envoy_config_core_v3.SubstitutionFormatString{
ContentType: "text/plain; charset=UTF-8",
Format: &envoy_config_core_v3.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
// just return the json body for plain text
InlineBytes: jsonBody,
},
},
},
},
HeadersToAdd: headers,
},
HeadersToAdd: headers,
}},
{
Filter: &envoy_config_accesslog_v3.AccessLogFilter{
FilterSpecifier: &envoy_config_accesslog_v3.AccessLogFilter_AndFilter{
AndFilter: &envoy_config_accesslog_v3.AndFilter{
Filters: []*envoy_config_accesslog_v3.AccessLogFilter{
{FilterSpecifier: responseFlagFilter},
{FilterSpecifier: &envoy_config_accesslog_v3.AccessLogFilter_MetadataFilter{
MetadataFilter: &envoy_config_accesslog_v3.MetadataFilter{
Matcher: buildLocalReplyTypeMatcher("json"),
},
}},
},
},
},
},
BodyFormatOverride: &envoy_config_core_v3.SubstitutionFormatString{
ContentType: "application/json; charset=UTF-8",
Format: &envoy_config_core_v3.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: jsonBody,
},
},
},
},
HeadersToAdd: headers,
},
{
Filter: &envoy_config_accesslog_v3.AccessLogFilter{
FilterSpecifier: responseFlagFilter,
},
BodyFormatOverride: &envoy_config_core_v3.SubstitutionFormatString{
ContentType: "text/html; charset=UTF-8",
Format: &envoy_config_core_v3.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: htmlBody,
},
},
},
},
HeadersToAdd: headers,
},
},
}, nil
}
func applyGlobalHTTPConnectionManagerOptions(hcm *envoy_http_connection_manager.HttpConnectionManager) {
func (b *Builder) applyGlobalHTTPConnectionManagerOptions(hcm *envoy_http_connection_manager.HttpConnectionManager) {
if hcm.InternalAddressConfig == nil {
// see doc comment on InternalAddressConfig for details
hcm.InternalAddressConfig = &envoy_http_connection_manager.HttpConnectionManager_InternalAddressConfig{
CidrRanges: []*envoy_config_core_v3.CidrRange{
// localhost
{AddressPrefix: "127.0.0.1", PrefixLen: wrapperspb.UInt32(32)},
ranges := []*envoy_config_core_v3.CidrRange{
// localhost
{AddressPrefix: "127.0.0.1", PrefixLen: wrapperspb.UInt32(32)},
// RFC1918
{AddressPrefix: "10.0.0.0", PrefixLen: wrapperspb.UInt32(8)},
{AddressPrefix: "192.168.0.0", PrefixLen: wrapperspb.UInt32(16)},
{AddressPrefix: "172.16.0.0", PrefixLen: wrapperspb.UInt32(12)},
}
if b.addIPV6InternalRanges {
ranges = append(ranges, []*envoy_config_core_v3.CidrRange{
// Localhost IPv6
{AddressPrefix: "::1", PrefixLen: wrapperspb.UInt32(128)},
// RFC1918
{AddressPrefix: "10.0.0.0", PrefixLen: wrapperspb.UInt32(8)},
{AddressPrefix: "192.168.0.0", PrefixLen: wrapperspb.UInt32(16)},
{AddressPrefix: "172.16.0.0", PrefixLen: wrapperspb.UInt32(12)},
// RFC4193
{AddressPrefix: "fd00::", PrefixLen: wrapperspb.UInt32(8)},
},
}...)
}
// see doc comment on InternalAddressConfig for details
hcm.InternalAddressConfig = &envoy_http_connection_manager.HttpConnectionManager_InternalAddressConfig{
CidrRanges: ranges,
}
}
}

View file

@ -21,6 +21,18 @@ func Test_buildLocalReplyConfig(t *testing.T) {
lrc, err := b.buildLocalReplyConfig(opts)
require.NoError(t, err)
tmpl := string(lrc.Mappers[0].GetBodyFormatOverride().GetTextFormatSource().GetInlineBytes())
assert.Equal(t, `{
"requestId": "%STREAM_ID%",
"status": "%RESPONSE_CODE%",
"statusText": "%RESPONSE_CODE_DETAILS%"
}`, tmpl)
tmpl = string(lrc.Mappers[1].GetBodyFormatOverride().GetTextFormatSource().GetInlineBytes())
assert.Equal(t, `{
"requestId": "%STREAM_ID%",
"status": "%RESPONSE_CODE%",
"statusText": "%RESPONSE_CODE_DETAILS%"
}`, tmpl)
tmpl = string(lrc.Mappers[2].GetBodyFormatOverride().GetTextFormatSource().GetInlineBytes())
assert.Equal(t, `<!DOCTYPE html>
<html lang="en">
<head>

View file

@ -9,7 +9,7 @@ import (
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
const listenerBufferLimit uint32 = 32 * 1024

View file

@ -51,7 +51,7 @@ func (b *Builder) buildEnvoyAdminHTTPConnectionManagerFilter() *envoy_config_lis
},
}})
return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
return b.HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "envoy-admin",
RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{

View file

@ -98,7 +98,7 @@ func (b *Builder) buildGRPCHTTPConnectionManagerFilter() *envoy_config_listener_
Routes: routes,
}})
return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
return b.HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "grpc_ingress",
// limit request first byte to last byte time

View file

@ -164,6 +164,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
LuaFilter(luascripts.ExtAuthzSetCookie),
LuaFilter(luascripts.CleanUpstream),
LuaFilter(luascripts.RewriteHeaders),
LuaFilter(luascripts.LocalReplyType),
}
// if we support http3 and this is the non-quic listener, add an alt-svc header indicating h3 is available
if !useQUIC && cfg.Options.CodecType == config.CodecTypeHTTP3 {
@ -207,9 +208,6 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
mgr.CodecType = envoy_extensions_filters_network_http_connection_manager.HttpConnectionManager_AUTO
} else if cfg.Options.GetCodecType() == config.CodecTypeAuto || cfg.Options.GetCodecType() == config.CodecTypeHTTP2 {
mgr.CodecType = cfg.Options.GetCodecType().ToEnvoy()
mgr.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{
AllowConnect: true,
}
} else {
mgr.CodecType = cfg.Options.GetCodecType().ToEnvoy()
}
@ -236,7 +234,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
}
}
return HTTPConnectionManagerFilter(mgr), nil
return b.HTTPConnectionManagerFilter(mgr), nil
}
func newListenerAccessLog() *envoy_config_accesslog_v3.AccessLog {

View file

@ -12,7 +12,7 @@ import (
)
func Test_requireProxyProtocol(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", nil, nil)
b := New("local-grpc", "local-http", "local-metrics", nil, nil, true)
t.Run("required", func(t *testing.T) {
li, err := b.buildMainListener(context.Background(), &config.Config{Options: &config.Options{
UseProxyProtocol: true,

View file

@ -121,7 +121,7 @@ func (b *Builder) buildMetricsHTTPConnectionManagerFilter() *envoy_config_listen
},
}})
return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
return b.HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "metrics",
RouteSpecifier: &envoy_http_connection_manager.HttpConnectionManager_RouteConfig{

View file

@ -51,7 +51,7 @@ func TestBuildListeners(t *testing.T) {
OutboundPort: "10003",
MetricsPort: "10004",
}
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
t.Run("enable grpc by default", func(t *testing.T) {
cfg := cfg.Clone()
lis, err := b.BuildListeners(ctx, cfg, false)
@ -103,7 +103,7 @@ func TestBuildListeners(t *testing.T) {
}]
}
}
}`, httpConfig.Get("httpFilters.6").String(),
}`, httpConfig.Get("httpFilters.7").String(),
"should add alt-svc header")
case "quic-ingress":
hasQUIC = true
@ -125,7 +125,7 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
certFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-crt-5a353247453159375849565a.pem")
keyFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-key-3159554e32473758435257364b.pem")
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
li, err := b.buildMetricsListener(&config.Config{
Options: &config.Options{
MetricsAddr: "127.0.0.1:9902",
@ -143,7 +143,7 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
}
func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", nil, nil)
b := New("local-grpc", "local-http", "local-metrics", nil, nil, true)
options := config.NewDefaultOptions()
options.SkipXffAppend = true

View file

@ -14,6 +14,7 @@ var luascripts struct {
RemoveImpersonateHeaders string
RewriteHeaders string
SetClientCertificateMetadata string
LocalReplyType string
}
func init() {
@ -23,6 +24,7 @@ func init() {
"luascripts/remove-impersonate-headers.lua": &luascripts.RemoveImpersonateHeaders,
"luascripts/rewrite-headers.lua": &luascripts.RewriteHeaders,
"luascripts/set-client-certificate-metadata.lua": &luascripts.SetClientCertificateMetadata,
"luascripts/local-reply-type.lua": &luascripts.LocalReplyType,
}
err := fs.WalkDir(luaFS, "luascripts", func(p string, d fs.DirEntry, err error) error {

View file

@ -11,8 +11,9 @@ import (
)
func TestLuaCleanUpstream(t *testing.T) {
L := lua.NewState()
defer L.Close()
t.Parallel()
L := newLua(t)
bs, err := luaFS.ReadFile("luascripts/clean-upstream.lua")
require.NoError(t, err)
@ -46,9 +47,52 @@ func TestLuaCleanUpstream(t *testing.T) {
}, headers)
}
func TestLuaLocalReplyContentType(t *testing.T) {
t.Parallel()
L := newLua(t)
bs, err := luaFS.ReadFile("luascripts/local-reply-type.lua")
require.NoError(t, err)
err = L.DoString(string(bs))
require.NoError(t, err)
for _, tc := range []struct {
accept string
expect string
}{
{"text/html", "html"},
{"application/json", "json"},
{"text/plain", "plain"},
{"text/plain,text/html", "plain"},
{"text/plain;q=0.8,text/html;q=0.9", "html"},
{"application/json;q=0.8,text/*;q=0.9", "html"},
} {
headers := map[string]string{
"accept": tc.accept,
}
dynamicMetadata := map[string]map[string]any{}
handle := newLuaRequestHandle(L, headers, dynamicMetadata)
err = L.CallByParam(lua.P{
Fn: L.GetGlobal("envoy_on_request"),
NRet: 0,
Protect: true,
}, handle)
require.NoError(t, err)
assert.Equal(t, map[string]map[string]any{
"envoy.filters.http.lua": {
"pomerium_local_reply_type": tc.expect,
},
}, dynamicMetadata)
}
}
func TestLuaRewriteHeaders(t *testing.T) {
L := lua.NewState()
defer L.Close()
t.Parallel()
L := newLua(t)
bs, err := luaFS.ReadFile("luascripts/rewrite-headers.lua")
require.NoError(t, err)
@ -85,6 +129,39 @@ func TestLuaRewriteHeaders(t *testing.T) {
assert.Equal(t, "https://frontend/one/some/uri/", headers["Location"])
}
func newLua(t *testing.T) *lua.LState {
L := lua.NewState()
t.Cleanup(L.Close)
L.SetGlobal("print", L.NewFunction(func(L *lua.LState) int {
var args []any
top := L.GetTop()
for i := 1; i <= top; i++ {
args = append(args, fromLua(L, L.Get(i)))
}
t.Log(args...)
return 0
}))
return L
}
func newLuaRequestHandle(L *lua.LState,
headers map[string]string,
dynamicMetadata map[string]map[string]any,
) lua.LValue {
return newLuaType(L, map[string]lua.LGFunction{
"headers": func(L *lua.LState) int {
L.Push(newLuaHeaders(L, headers))
return 1
},
"streamInfo": func(L *lua.LState) int {
L.Push(newLuaStreamInfo(L, dynamicMetadata))
return 1
},
})
}
func newLuaResponseHandle(L *lua.LState,
headers map[string]string,
metadata map[string]any,

View file

@ -0,0 +1,76 @@
-- This filter interprets the accept header of an incoming request and attempts to map it to
-- a metadata value of either "html", "json" or "plain". This metadata value is used to format
-- local replies in a format the client expects.
function parse_accept_header(header_value)
-- returns a table with a type field, the table is sorted by position and weight
if header_value == nil then
return {}
end
local content_types = {}
local start_idx = 1
local position = 1
while true do
local end_idx = string.find(header_value, ",", start_idx)
local segment
if end_idx == nil then
segment = string.sub(header_value, start_idx)
else
segment = string.sub(header_value, start_idx, end_idx-1)
end
local mime_type = segment
local q = 1.0
local semicolon_idx = string.find(segment, ';')
if semicolon_idx ~= nil then
mime_type = string.sub(segment, 1, semicolon_idx-1)
q_idx = string.find(segment, "q=", semicolon_idx+1)
if q_idx ~= nil then
q_str = string.sub(segment, q_idx+2)
q = tonumber(q_str)
end
end
table.insert(content_types, { type=mime_type, q=q, position=position })
position = position+1
if end_idx == nil then
break
else
start_idx = end_idx+1
end
end
table.sort(content_types, function(a,b)
if a.q == b.q then
return a.position < b.position
end
return a.q > b.q
end)
return content_types
end
function envoy_on_request(request_handle)
local headers = request_handle:headers()
local dynamic_meta = request_handle:streamInfo():dynamicMetadata()
local content_types = parse_accept_header(headers:get("accept"))
for _, v in pairs(content_types) do
if v.type == "text/html" or v.type == "text/*" then
dynamic_meta:set("envoy.filters.http.lua", "pomerium_local_reply_type", "html")
return
elseif v.type == "text/plain" then
dynamic_meta:set("envoy.filters.http.lua", "pomerium_local_reply_type", "plain")
return
elseif v.type == "application/json" or v.type == "application/*" then
dynamic_meta:set("envoy.filters.http.lua", "pomerium_local_reply_type", "json")
return
end
end
-- if nothing matched, just return html
dynamic_meta:set("envoy.filters.http.lua", "pomerium_local_reply_type", "html")
end
function envoy_on_response(response_handle)
-- unused
end

View file

@ -0,0 +1,23 @@
package envoyconfig
import envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
func buildLocalReplyTypeMatcher(localReplyType string) *envoy_type_matcher_v3.MetadataMatcher {
return &envoy_type_matcher_v3.MetadataMatcher{
Filter: "envoy.filters.http.lua",
Path: []*envoy_type_matcher_v3.MetadataMatcher_PathSegment{{
Segment: &envoy_type_matcher_v3.MetadataMatcher_PathSegment_Key{
Key: "pomerium_local_reply_type",
},
}},
Value: &envoy_type_matcher_v3.ValueMatcher{
MatchPattern: &envoy_type_matcher_v3.ValueMatcher_StringMatch{
StringMatch: &envoy_type_matcher_v3.StringMatcher{
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
Exact: localReplyType,
},
},
},
},
}
}

View file

@ -42,7 +42,7 @@ func (b *Builder) buildOutboundListener(cfg *config.Config) (*envoy_config_liste
func (b *Builder) buildOutboundHTTPConnectionManager() *envoy_config_listener_v3.Filter {
rc := b.buildOutboundRouteConfiguration()
return HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
return b.HTTPConnectionManagerFilter(&envoy_http_connection_manager.HttpConnectionManager{
CodecType: envoy_http_connection_manager.HttpConnectionManager_AUTO,
StatPrefix: "grpc_egress",
// limit request first byte to last byte time

View file

@ -7,7 +7,7 @@ import (
)
func Test_buildOutboundRoutes(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", nil, nil)
b := New("local-grpc", "local-http", "local-metrics", nil, nil, true)
routes := b.buildOutboundRoutes()
testutil.AssertProtoJSONEqual(t, `[
{

View file

@ -1,11 +1,17 @@
package envoyconfig_test
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"sync/atomic"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/credentials/insecure"
@ -28,9 +34,9 @@ func TestH2C(t *testing.T) {
http := up.Route().
From(env.SubdomainURL("grpc-http")).
To(values.Bind(up.Port(), func(port int) string {
To(values.Bind(up.Addr(), func(addr string) string {
// override the target protocol to use http://
return fmt.Sprintf("http://127.0.0.1:%d", port)
return fmt.Sprintf("http://%s", addr)
})).
Policy(func(p *config.Policy) { p.AllowPublicUnauthenticatedAccess = true })
@ -118,6 +124,234 @@ func TestHTTP(t *testing.T) {
})
}
func TestTCPTunnel(t *testing.T) {
env := testenv.New(t, testenv.Debug())
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
up := upstreams.TCP()
routeH1 := up.Route().
From(env.SubdomainURL("h1")).
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
routeH2 := up.Route().
From(env.SubdomainURL("h2")).
Policy(func(p *config.Policy) {
p.AllowWebsockets = true
}).
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
up.Handle(func(_ context.Context, c net.Conn) error {
c.SetReadDeadline(time.Now().Add(1 * time.Second))
buf := make([]byte, 8)
n, err := c.Read(buf)
require.NoError(t, err)
assert.Equal(t, string(buf[:n]), "hello")
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
_, err = c.Write([]byte("world"))
require.NoError(t, err)
return nil
})
env.AddUpstream(up)
env.Start()
snippets.WaitStartupComplete(env)
t.Run("http1", func(t *testing.T) {
assert.NoError(t, up.Dial(routeH1, func(_ context.Context, c net.Conn) error {
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
_, err := c.Write([]byte("hello"))
require.NoError(t, err)
buf := make([]byte, 8)
c.SetReadDeadline(time.Now().Add(1 * time.Second))
n, err := c.Read(buf)
require.NoError(t, err)
assert.Equal(t, string(buf[:n]), "world")
return nil
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(upstreams.DialHTTP1)))
})
t.Run("http2", func(t *testing.T) {
assert.NoError(t, up.Dial(routeH2, func(_ context.Context, c net.Conn) error {
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
_, err := c.Write([]byte("hello"))
require.NoError(t, err)
buf := make([]byte, 8)
c.SetReadDeadline(time.Now().Add(1 * time.Second))
n, err := c.Read(buf)
require.NoError(t, err)
assert.Equal(t, string(buf[:n]), "world")
return nil
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(upstreams.DialHTTP2)))
})
}
func BenchmarkHTTP1TCPTunnel(b *testing.B) {
env := testenv.New(b, testenv.Silent())
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
up := upstreams.TCP()
h1 := up.Route().
From(env.SubdomainURL("bench-h1")).
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
env.AddUpstream(up)
env.Start()
snippets.WaitStartupComplete(env)
b.Run("http1", func(b *testing.B) {
benchmarkTCP(b, up, h1, tcpBenchmarkParams{
msgLen: 512,
protocol: upstreams.DialHTTP1,
})
})
}
func BenchmarkHTTP2TCPTunnel(b *testing.B) {
env := testenv.New(b, testenv.Silent())
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
up := upstreams.TCP()
h2 := up.Route().
From(env.SubdomainURL("bench-h2")).
Policy(func(p *config.Policy) {
p.AllowWebsockets = true
}).
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
env.AddUpstream(up)
env.Start()
snippets.WaitStartupComplete(env)
b.Run("http2", func(b *testing.B) {
benchmarkTCP(b, up, h2, tcpBenchmarkParams{
msgLen: 512,
protocol: upstreams.DialHTTP2,
})
})
}
type tcpBenchmarkParams struct {
msgLen int
protocol upstreams.Protocol
}
func benchmarkTCP(b *testing.B, up upstreams.TCPUpstream, route testenv.Route, params tcpBenchmarkParams) {
sendMsg := func(c net.Conn, buf []byte) error {
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
_, err := c.Write(buf)
if err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
}
return err
}
recvMsg := func(c net.Conn, buf []byte) error {
c.SetReadDeadline(time.Now().Add(1 * time.Second))
for read := 0; read != len(buf); {
n, err := c.Read(buf)
read += n
if err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
}
return nil
}
up.Handle(func(_ context.Context, c net.Conn) error {
for {
buf := make([]byte, params.msgLen)
if err := recvMsg(c, buf[:]); err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
if err := sendMsg(c, buf[:]); err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
}
})
var threads atomic.Int32
var requests atomic.Int32
var bytes atomic.Int64
start := time.Now()
b.RunParallel(func(p *testing.PB) {
threads.Add(1)
require.NoError(b, up.Dial(route, func(_ context.Context, c net.Conn) error {
buf := make([]byte, params.msgLen)
for p.Next() {
requests.Add(1)
bytes.Add(int64(params.msgLen))
require.NoError(b, sendMsg(c, buf[:]))
require.NoError(b, recvMsg(c, buf[:]))
}
return nil
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(params.protocol)))
})
duration := time.Since(start)
b.Logf("sent %d requests over %d parallel connections in %s", requests.Load(), threads.Load(), duration)
b.Logf("throughput: %f bytes/s", float64(bytes.Load())/duration.Seconds())
}
func TestHttp1Websocket(t *testing.T) {
env := testenv.New(t)
up := upstreams.HTTP(nil)
up.HandleWS("/ws", websocket.Upgrader{}, func(conn *websocket.Conn) error {
for {
mt, message, err := conn.ReadMessage()
if err != nil {
return err
}
// echo the message back
err = conn.WriteMessage(mt, message)
if err != nil {
return err
}
}
})
route := up.Route().
From(env.SubdomainURL("ws-test")).
Policy(func(p *config.Policy) {
p.AllowPublicUnauthenticatedAccess = true
p.AllowWebsockets = true
})
env.AddUpstream(up)
env.Start()
snippets.WaitStartupComplete(env)
assert.NoError(t, up.DialWS(route, func(conn *websocket.Conn) error {
if err := conn.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil {
return err
}
if err := conn.WriteMessage(websocket.TextMessage, []byte("hello world")); err != nil {
return err
}
if err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
return err
}
mt, bytes, err := conn.ReadMessage()
if err := err; err != nil {
return err
}
assert.Equal(t, websocket.TextMessage, mt)
assert.Equal(t, "hello world", string(bytes))
return nil
}, upstreams.Path("/ws")))
}
func TestClientCert(t *testing.T) {
env := testenv.New(t)
env.Add(scenarios.DownstreamMTLS(config.MTLSEnforcementRejectConnection))

View file

@ -11,8 +11,8 @@ import (
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// BuildRouteConfigurations builds the route configurations for the RDS service.
@ -50,14 +50,14 @@ func (b *Builder) buildMainRouteConfiguration(
return nil, err
}
allHosts, err := getAllRouteableHosts(cfg.Options, cfg.Options.Addr)
allHosts, mcpHosts, err := getAllRouteableHosts(cfg.Options, cfg.Options.Addr)
if err != nil {
return nil, err
}
var virtualHosts []*envoy_config_route_v3.VirtualHost
for _, host := range allHosts {
vh, err := b.buildVirtualHost(cfg.Options, host, host)
vh, err := b.buildVirtualHost(cfg.Options, host, host, mcpHosts[host])
if err != nil {
return nil, err
}
@ -88,7 +88,7 @@ func (b *Builder) buildMainRouteConfiguration(
}
}
vh, err := b.buildVirtualHost(cfg.Options, "catch-all", "*")
vh, err := b.buildVirtualHost(cfg.Options, "catch-all", "*", false)
if err != nil {
return nil, err
}
@ -106,21 +106,28 @@ func (b *Builder) buildMainRouteConfiguration(
return rc, nil
}
func getAllRouteableHosts(options *config.Options, addr string) ([]string, error) {
func getAllRouteableHosts(options *config.Options, addr string) ([]string, map[string]bool, error) {
allHosts := set.NewTreeSet(cmp.Compare[string])
mcpHosts := make(map[string]bool)
if addr == options.Addr {
hosts, err := options.GetAllRouteableHTTPHosts()
hosts, hostsMCP, err := options.GetAllRouteableHTTPHosts()
if err != nil {
return nil, err
return nil, nil, err
}
allHosts.InsertSlice(hosts)
// Merge any MCP hosts
for host, isMCP := range hostsMCP {
if isMCP {
mcpHosts[host] = true
}
}
}
if addr == options.GetGRPCAddr() {
hosts, err := options.GetAllRouteableGRPCHosts()
if err != nil {
return nil, err
return nil, nil, err
}
allHosts.InsertSlice(hosts)
}
@ -131,7 +138,7 @@ func getAllRouteableHosts(options *config.Options, addr string) ([]string, error
filtered = append(filtered, host)
}
}
return filtered, nil
return filtered, mcpHosts, nil
}
func newRouteConfiguration(name string, virtualHosts []*envoy_config_route_v3.VirtualHost) *envoy_config_route_v3.RouteConfiguration {

View file

@ -32,7 +32,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
},
},
}}
b := New("grpc", "http", "metrics", filemgr.NewManager(), nil)
b := New("grpc", "http", "metrics", filemgr.NewManager(), nil, true)
routeConfiguration, err := b.buildMainRouteConfiguration(ctx, cfg)
assert.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `{
@ -195,7 +195,7 @@ func Test_getAllDomains(t *testing.T) {
}
t.Run("routable", func(t *testing.T) {
t.Run("http", func(t *testing.T) {
actual, err := getAllRouteableHosts(options, "127.0.0.1:9000")
actual, _, err := getAllRouteableHosts(options, "127.0.0.1:9000")
require.NoError(t, err)
expect := []string{
"a.example.com",
@ -214,7 +214,7 @@ func Test_getAllDomains(t *testing.T) {
assert.Equal(t, expect, actual)
})
t.Run("grpc", func(t *testing.T) {
actual, err := getAllRouteableHosts(options, "127.0.0.1:9001")
actual, _, err := getAllRouteableHosts(options, "127.0.0.1:9001")
require.NoError(t, err)
expect := []string{
"authorize.example.com:9001",
@ -225,7 +225,7 @@ func Test_getAllDomains(t *testing.T) {
t.Run("both", func(t *testing.T) {
newOptions := *options
newOptions.GRPCAddr = newOptions.Addr
actual, err := getAllRouteableHosts(&newOptions, "127.0.0.1:9000")
actual, _, err := getAllRouteableHosts(&newOptions, "127.0.0.1:9000")
require.NoError(t, err)
expect := []string{
"a.example.com",
@ -252,7 +252,7 @@ func Test_getAllDomains(t *testing.T) {
options.Policies = []config.Policy{
{From: "https://a.example.com"},
}
actual, err := getAllRouteableHosts(options, ":443")
actual, _, err := getAllRouteableHosts(options, ":443")
require.NoError(t, err)
assert.Equal(t, []string{"a.example.com"}, actual)
})
@ -275,7 +275,6 @@ func Test_urlMatchesHost(t *testing.T) {
{"non standard port", "http://example.com:81", "example.com", false},
{"non standard host port", "http://example.com:81", "example.com:80", false},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

View file

@ -50,6 +50,7 @@ func (b *Builder) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
func (b *Builder) buildPomeriumHTTPRoutes(
options *config.Options,
host string,
isMCPHost bool,
) ([]*envoy_config_route_v3.Route, error) {
var routes []*envoy_config_route_v3.Route
@ -60,6 +61,7 @@ func (b *Builder) buildPomeriumHTTPRoutes(
return nil, err
}
if !isFrontingAuthenticate {
// Add common routes
routes = append(routes,
b.buildControlPlanePathRoute(options, "/ping"),
b.buildControlPlanePathRoute(options, "/healthz"),
@ -68,6 +70,11 @@ func (b *Builder) buildPomeriumHTTPRoutes(
b.buildControlPlanePathRoute(options, "/.well-known/pomerium"),
b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/"),
)
// Only add oauth-authorization-server route if there's an MCP policy for this host
if isMCPHost {
routes = append(routes, b.buildControlPlanePathRoute(options, "/.well-known/oauth-authorization-server"))
}
}
authRoutes, err := b.buildPomeriumAuthenticateHTTPRoutes(options, host)

View file

@ -104,7 +104,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
AuthenticateURLString: "https://authenticate.example.com",
AuthenticateCallbackPath: "/oauth2/callback",
}
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com", false)
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[
@ -125,7 +125,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
AuthenticateURLString: "https://authenticate.example.com",
AuthenticateCallbackPath: "/oauth2/callback",
}
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
routes, err := b.buildPomeriumHTTPRoutes(options, "authenticate.example.com", false)
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, "null", routes)
})
@ -2244,3 +2244,107 @@ func mustParseURL(t *testing.T, str string) *url.URL {
func ptr[T any](v T) *T {
return &v
}
func Test_buildPomeriumHTTPRoutesWithMCP(t *testing.T) {
routeString := func(typ, name string) string {
str := `{
"name": "pomerium-` + typ + `-` + name + `",
"decorator": {
"operation": "internal: ${method} ${host}${path}"
},
"match": {
"` + typ + `": "` + name + `"
},
"responseHeadersToAdd": [
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
}
},
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
}
],
"route": {
"cluster": "pomerium-control-plane-http"
},
"typedPerFilterConfig": {
"envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"checkSettings": {
"contextExtensions": {
"internal": "true",
"route_id": "0"
}
}
}
}
}`
return str
}
t.Run("without MCP policy", func(t *testing.T) {
b := &Builder{filemgr: filemgr.NewManager()}
options := &config.Options{
Services: "all",
AuthenticateURLString: "https://authenticate.example.com",
Policies: []config.Policy{
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
},
},
}
routes, err := b.buildPomeriumHTTPRoutes(options, "example.com", false)
require.NoError(t, err)
hasOAuthServer := false
for _, route := range routes {
if route.GetMatch().GetPath() == "/.well-known/oauth-authorization-server" {
hasOAuthServer = true
}
}
assert.False(t, hasOAuthServer, "/.well-known/oauth-authorization-server route should NOT be present")
})
t.Run("with MCP policy", func(t *testing.T) {
b := &Builder{filemgr: filemgr.NewManager()}
options := &config.Options{
Services: "all",
AuthenticateURLString: "https://authenticate.example.com",
Policies: []config.Policy{
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
},
{
From: "https://mcp.example.com",
To: mustParseWeightedURLs(t, "https://mcp-backend.example.com"),
MCP: &config.MCP{}, // This marks the policy as an MCP policy
},
},
}
routes, err := b.buildPomeriumHTTPRoutes(options, "example.com", true)
require.NoError(t, err)
// Verify the expected route structures
testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/ping")+`,
`+routeString("path", "/healthz")+`,
`+routeString("path", "/.pomerium")+`,
`+routeString("prefix", "/.pomerium/")+`,
`+routeString("path", "/.well-known/pomerium")+`,
`+routeString("prefix", "/.well-known/pomerium/")+`,
`+routeString("path", "/.well-known/oauth-authorization-server")+`
]`, routes)
})
}

View file

@ -64,9 +64,7 @@
"statusOnError": {
"code": "InternalServerError"
},
"metadataContextNamespaces": [
"com.pomerium.client-certificate-info"
]
"metadataContextNamespaces": ["com.pomerium.client-certificate-info"]
}
},
{
@ -96,6 +94,15 @@
}
}
},
{
"name": "envoy.filters.http.lua",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
"defaultSourceCode": {
"inlineString": "-- This filter interprets the accept header of an incoming request and attempts to map it to\n-- a metadata value of either \"html\", \"json\" or \"plain\". This metadata value is used to format\n-- local replies in a format the client expects.\n\nfunction parse_accept_header(header_value)\n -- returns a table with a type field, the table is sorted by position and weight\n if header_value == nil then\n return {}\n end\n\n local content_types = {}\n local start_idx = 1\n local position = 1\n while true do\n local end_idx = string.find(header_value, \",\", start_idx)\n local segment\n if end_idx == nil then\n segment = string.sub(header_value, start_idx)\n else\n segment = string.sub(header_value, start_idx, end_idx-1)\n end\n\n local mime_type = segment\n local q = 1.0\n local semicolon_idx = string.find(segment, ';')\n if semicolon_idx ~= nil then\n mime_type = string.sub(segment, 1, semicolon_idx-1)\n q_idx = string.find(segment, \"q=\", semicolon_idx+1)\n if q_idx ~= nil then\n q_str = string.sub(segment, q_idx+2)\n q = tonumber(q_str)\n end\n end\n\n table.insert(content_types, { type=mime_type, q=q, position=position })\n position = position+1\n\n if end_idx == nil then\n break\n else\n start_idx = end_idx+1\n end\n end\n table.sort(content_types, function(a,b)\n if a.q == b.q then\n return a.position < b.position\n end\n return a.q > b.q\n end)\n return content_types\nend\n\nfunction envoy_on_request(request_handle)\n local headers = request_handle:headers()\n local dynamic_meta = request_handle:streamInfo():dynamicMetadata()\n\n local content_types = parse_accept_header(headers:get(\"accept\"))\n for _, v in pairs(content_types) do\n if v.type == \"text/html\" or v.type == \"text/*\" then\n dynamic_meta:set(\"envoy.filters.http.lua\", \"pomerium_local_reply_type\", \"html\")\n return\n elseif v.type == \"text/plain\" then\n dynamic_meta:set(\"envoy.filters.http.lua\", \"pomerium_local_reply_type\", \"plain\")\n return\n elseif v.type == \"application/json\" or v.type == \"application/*\" then\n dynamic_meta:set(\"envoy.filters.http.lua\", \"pomerium_local_reply_type\", \"json\")\n return\n end\n end\n -- if nothing matched, just return html\n dynamic_meta:set(\"envoy.filters.http.lua\", \"pomerium_local_reply_type\", \"html\")\nend\n\nfunction envoy_on_response(response_handle)\n -- unused\nend\n"
}
}
},
{
"name": "envoy.filters.http.router",
"typedConfig": {
@ -113,11 +120,160 @@
}
}
},
"http2ProtocolOptions": {
"allowConnect": true
},
"localReplyConfig": {
"mappers": [
{
"filter": {
"andFilter": {
"filters": [
{
"responseFlagFilter": {
"flags": [
"DC",
"DF",
"DI",
"DO",
"DPE",
"DT",
"FI",
"IH",
"LH",
"LR",
"NC",
"NFCF",
"NR",
"OM",
"RFCF",
"RL",
"RLSE",
"SI",
"UC",
"UF",
"UH",
"UMSDR",
"UO",
"UPE",
"UR",
"URX",
"UT"
]
}
},
{
"metadataFilter": {
"matcher": {
"filter": "envoy.filters.http.lua",
"path": [{ "key": "pomerium_local_reply_type" }],
"value": {
"stringMatch": {
"exact": "plain"
}
}
}
}
}
]
}
},
"bodyFormatOverride": {
"contentType": "text/plain; charset=UTF-8",
"textFormatSource": {
"inlineBytes": "ewogICJyZXF1ZXN0SWQiOiAiJVNUUkVBTV9JRCUiLAogICJzdGF0dXMiOiAiJVJFU1BPTlNFX0NPREUlIiwKICAic3RhdHVzVGV4dCI6ICIlUkVTUE9OU0VfQ09ERV9ERVRBSUxTJSIKfQ=="
}
},
"headersToAdd": [
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
}
},
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
}
]
},
{
"filter": {
"andFilter": {
"filters": [
{
"responseFlagFilter": {
"flags": [
"DC",
"DF",
"DI",
"DO",
"DPE",
"DT",
"FI",
"IH",
"LH",
"LR",
"NC",
"NFCF",
"NR",
"OM",
"RFCF",
"RL",
"RLSE",
"SI",
"UC",
"UF",
"UH",
"UMSDR",
"UO",
"UPE",
"UR",
"URX",
"UT"
]
}
},
{
"metadataFilter": {
"matcher": {
"filter": "envoy.filters.http.lua",
"path": [{ "key": "pomerium_local_reply_type" }],
"value": {
"stringMatch": {
"exact": "json"
}
}
}
}
}
]
}
},
"bodyFormatOverride": {
"contentType": "application/json; charset=UTF-8",
"textFormatSource": {
"inlineBytes": "ewogICJyZXF1ZXN0SWQiOiAiJVNUUkVBTV9JRCUiLAogICJzdGF0dXMiOiAiJVJFU1BPTlNFX0NPREUlIiwKICAic3RhdHVzVGV4dCI6ICIlUkVTUE9OU0VfQ09ERV9ERVRBSUxTJSIKfQ=="
}
},
"headersToAdd": [
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
}
},
{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
}
]
},
{
"bodyFormatOverride": {
"contentType": "text/html; charset=UTF-8",
@ -234,10 +390,6 @@
"addressPrefix": "127.0.0.1",
"prefixLen": 32
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "10.0.0.0",
"prefixLen": 8
@ -250,6 +402,10 @@
"addressPrefix": "172.16.0.0",
"prefixLen": 12
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "fd00::",
"prefixLen": 8

View file

@ -61,10 +61,6 @@
"addressPrefix": "127.0.0.1",
"prefixLen": 32
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "10.0.0.0",
"prefixLen": 8
@ -77,6 +73,10 @@
"addressPrefix": "172.16.0.0",
"prefixLen": 12
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "fd00::",
"prefixLen": 8

View file

@ -82,7 +82,7 @@ func TestValidateCertificate(t *testing.T) {
}
func Test_buildDownstreamTLSContext(t *testing.T) {
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil, true)
cacheDir, _ := os.UserCacheDir()
clientCAFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "client-ca-4e4c564e5a36544a4a33385a.pem")

View file

@ -13,13 +13,14 @@ import (
metadatav3 "github.com/envoyproxy/go-control-plane/envoy/type/metadata/v3"
envoy_tracing_v3 "github.com/envoyproxy/go-control-plane/envoy/type/tracing/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
extensions_trace_context "github.com/pomerium/envoy-custom/api/extensions/http/early_header_mutation/trace_context"
extensions_uuidx "github.com/pomerium/envoy-custom/api/extensions/request_id/uuidx"
extensions_pomerium_otel "github.com/pomerium/envoy-custom/api/extensions/tracers/pomerium_otel"
"github.com/pomerium/pomerium/config/otelconfig"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
func isTracingEnabled(cfg *otelconfig.Config) bool {

View file

@ -15,6 +15,7 @@ import (
"github.com/pomerium/pomerium/internal/middleware"
"github.com/pomerium/pomerium/internal/telemetry"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
metrics_const "github.com/pomerium/pomerium/pkg/metrics"
)
const (
@ -92,16 +93,20 @@ func (mgr *MetricsManager) updateServer(ctx context.Context, cfg *Config) {
mgr.handler = nil
if mgr.addr == "" {
log.Ctx(ctx).Info().Msg("metrics: http server disabled")
return
}
var labels map[string]string
if cfg.Options.IsRuntimeFlagSet(RuntimeFlagAddExtraMetricsLabels) {
labels = getCommonLabels(mgr.installationID)
}
mgr.endpoints = append(cfg.MetricsScrapeEndpoints,
MetricsScrapeEndpoint{
Name: "envoy",
URL: url.URL{Scheme: "http", Host: cfg.Options.MetricsAddr, Path: "/metrics/envoy"},
})
handler, err := metrics.PrometheusHandler(toInternalEndpoints(mgr.endpoints), mgr.installationID, defaultMetricsTimeout)
handler, err := metrics.PrometheusHandler(toInternalEndpoints(mgr.endpoints), defaultMetricsTimeout, labels)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("metrics: failed to create prometheus handler")
return
@ -128,3 +133,17 @@ func toInternalEndpoints(src []MetricsScrapeEndpoint) []metrics.ScrapeEndpoint {
}
return dst
}
func getCommonLabels(installationID string) map[string]string {
hostname, err := os.Hostname()
if err != nil {
hostname = "__none__"
}
m := map[string]string{
metrics_const.HostnameLabel: hostname,
}
if installationID != "" {
m[metrics_const.InstallationIDLabel] = installationID
}
return m
}

View file

@ -19,8 +19,8 @@ import (
"time"
envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/go-viper/mapstructure/v2"
goset "github.com/hashicorp/go-set/v3"
"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog"
"github.com/spf13/viper"
"github.com/volatiletech/null/v9"
@ -196,6 +196,12 @@ type Options struct {
// List of JWT claims to insert as x-pomerium-claim-* headers on proxied requests
JWTClaimsHeaders JWTClaimHeaders `mapstructure:"jwt_claims_headers" yaml:"jwt_claims_headers,omitempty"`
// JWTIssuerFormat controls the default format of the 'iss' claim in JWTs passed to upstream services.
// Possible values:
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
// - "uri": Issuer strings will be a complete URI, including the scheme and ending with a trailing slash.
JWTIssuerFormat JWTIssuerFormat `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
// BearerTokenFormat indicates how authorization bearer tokens are interepreted. Possible values:
// - "default": Only Bearer tokens prefixed with Pomerium- will be interpreted by Pomerium.
// - "idp_access_token": The Bearer token will be interpreted as an IdP access token.
@ -405,7 +411,7 @@ func checkConfigKeysErrors(configFile string, o *Options, unused []string) error
default:
evt = log.Ctx(ctx).Error()
}
evt.Str("config_file", configFile).Str("key", check.Key)
evt.Str("config-file", configFile).Str("key", check.Key)
if check.DocsURL != "" {
evt = evt.Str("help", check.DocsURL)
}
@ -415,7 +421,7 @@ func checkConfigKeysErrors(configFile string, o *Options, unused []string) error
// check for unknown runtime flags
for flag := range o.RuntimeFlags {
if _, ok := defaultRuntimeFlags[flag]; !ok {
log.Ctx(ctx).Error().Str("config_file", configFile).Str("flag", string(flag)).Msg("unknown runtime flag")
log.Ctx(ctx).Error().Str("config-file", configFile).Str("flag", string(flag)).Msg("unknown runtime flag")
}
}
@ -649,14 +655,11 @@ func (o *Options) Validate() error {
return fmt.Errorf("config: failed to parse headers: %w", err)
}
hasCert := false
if o.Cert != "" || o.Key != "" {
_, err := cryptutil.CertificateFromBase64(o.Cert, o.Key)
if err != nil {
return fmt.Errorf("config: bad cert base64 %w", err)
}
hasCert = true
}
for _, c := range o.CertificateData {
@ -664,7 +667,6 @@ func (o *Options) Validate() error {
if err != nil {
return fmt.Errorf("config: bad cert entry, cert is invalid: %w", err)
}
hasCert = true
}
for _, c := range o.CertificateFiles {
@ -672,7 +674,6 @@ func (o *Options) Validate() error {
if err != nil {
return fmt.Errorf("config: bad cert entry, file reference invalid. %w", err)
}
hasCert = true
}
if o.CertFile != "" || o.KeyFile != "" {
@ -680,7 +681,6 @@ func (o *Options) Validate() error {
if err != nil {
return fmt.Errorf("config: bad cert file %w", err)
}
hasCert = true
}
if err := o.DownstreamMTLS.validate(); err != nil {
@ -690,11 +690,6 @@ func (o *Options) Validate() error {
// strip quotes from redirect address (#811)
o.HTTPRedirectAddr = strings.Trim(o.HTTPRedirectAddr, `"'`)
if !o.InsecureServer && !hasCert && !o.AutocertOptions.Enable {
log.Ctx(ctx).Info().Msg("neither `autocert`, " +
"`insecure_server` or manually provided certificates were provided, server will be using a self-signed certificate")
}
if err := ValidateDNSLookupFamily(o.DNSLookupFamily); err != nil {
return fmt.Errorf("config: %w", err)
}
@ -761,6 +756,10 @@ func (o *Options) Validate() error {
}
}
if !o.JWTIssuerFormat.Valid() {
return fmt.Errorf("config: unsupported jwt_issuer_format value %q", o.JWTIssuerFormat)
}
return nil
}
@ -1274,23 +1273,27 @@ func (o *Options) GetAllRouteableGRPCHosts() ([]string, error) {
}
// GetAllRouteableHTTPHosts returns all the possible HTTP hosts handled by the Pomerium options.
func (o *Options) GetAllRouteableHTTPHosts() ([]string, error) {
func (o *Options) GetAllRouteableHTTPHosts() ([]string, map[string]bool, error) {
hosts := goset.NewTreeSet(cmp.Compare[string])
mcpHosts := make(map[string]bool)
if IsAuthenticate(o.Services) {
if o.AuthenticateInternalURLString != "" {
authenticateURL, err := o.GetInternalAuthenticateURL()
if err != nil {
return nil, err
return nil, nil, err
}
hosts.InsertSlice(urlutil.GetDomainsForURL(authenticateURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort)))
domains := urlutil.GetDomainsForURL(authenticateURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort))
hosts.InsertSlice(domains)
}
if o.AuthenticateURLString != "" {
authenticateURL, err := o.GetAuthenticateURL()
if err != nil {
return nil, err
return nil, nil, err
}
hosts.InsertSlice(urlutil.GetDomainsForURL(authenticateURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort)))
domains := urlutil.GetDomainsForURL(authenticateURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort))
hosts.InsertSlice(domains)
}
}
@ -1299,18 +1302,35 @@ func (o *Options) GetAllRouteableHTTPHosts() ([]string, error) {
for policy := range o.GetAllPolicies() {
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
if err != nil {
return nil, err
return nil, nil, err
}
domains := urlutil.GetDomainsForURL(fromURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort))
hosts.InsertSlice(domains)
// Track if the domains are associated with an MCP policy
if policy.IsMCP() {
for _, domain := range domains {
mcpHosts[domain] = true
}
}
hosts.InsertSlice(urlutil.GetDomainsForURL(fromURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort)))
if policy.TLSDownstreamServerName != "" {
tlsURL := fromURL.ResolveReference(&url.URL{Host: policy.TLSDownstreamServerName})
hosts.InsertSlice(urlutil.GetDomainsForURL(tlsURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort)))
tlsDomains := urlutil.GetDomainsForURL(tlsURL, !o.IsRuntimeFlagSet(RuntimeFlagMatchAnyIncomingPort))
hosts.InsertSlice(tlsDomains)
// Track if the TLS domains are associated with an MCP policy
if policy.IsMCP() {
for _, domain := range tlsDomains {
mcpHosts[domain] = true
}
}
}
}
}
return hosts.Slice(), nil
return hosts.Slice(), mcpHosts, nil
}
// GetClientSecret gets the client secret.
@ -1499,8 +1519,6 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
if settings.IdpAccessTokenAllowedAudiences != nil {
values := slices.Clone(settings.IdpAccessTokenAllowedAudiences.Values)
o.IDPAccessTokenAllowedAudiences = &values
} else {
o.IDPAccessTokenAllowedAudiences = nil
}
setSlice(&o.AuthorizeURLStrings, settings.AuthorizeServiceUrls)
set(&o.AuthorizeInternalURLString, settings.AuthorizeInternalServiceUrl)
@ -1510,10 +1528,13 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
set(&o.SigningKey, settings.SigningKey)
setMap(&o.SetResponseHeaders, settings.SetResponseHeaders)
setMap(&o.JWTClaimsHeaders, settings.JwtClaimsHeaders)
o.BearerTokenFormat = BearerTokenFormatFromPB(settings.BearerTokenFormat)
setOptional(&o.BearerTokenFormat, BearerTokenFormatFromPB(settings.BearerTokenFormat))
if len(settings.JwtGroupsFilter) > 0 {
o.JWTGroupsFilter = NewJWTGroupsFilter(settings.JwtGroupsFilter)
}
if f := JWTIssuerFormatFromPB(settings.JwtIssuerFormat); f != JWTIssuerFormatUnset {
o.JWTIssuerFormat = f
}
setDuration(&o.DefaultUpstreamTimeout, settings.DefaultUpstreamTimeout)
set(&o.MetricsAddr, settings.MetricsAddress)
set(&o.MetricsBasicAuth, settings.MetricsBasicAuth)
@ -1624,6 +1645,7 @@ func (o *Options) ToProto() *config.Config {
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
settings.BearerTokenFormat = o.BearerTokenFormat.ToPB()
settings.JwtGroupsFilter = o.JWTGroupsFilter.ToSlice()
settings.JwtIssuerFormat = o.JWTIssuerFormat.ToPB()
copyOptionalDuration(&settings.DefaultUpstreamTimeout, o.DefaultUpstreamTimeout)
copySrcToOptionalDest(&settings.MetricsAddress, &o.MetricsAddr)
copySrcToOptionalDest(&settings.MetricsBasicAuth, &o.MetricsBasicAuth)
@ -1859,13 +1881,6 @@ func compareByteSliceSlice(a, b [][]byte) int {
}
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
// NewAtomicOptions creates a new AtomicOptions.
func NewAtomicOptions() *atomicutil.Value[*Options] {
return atomicutil.NewValue(new(Options))

View file

@ -331,7 +331,7 @@ func Test_parsePolicyFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempFile, _ := os.CreateTemp("", "*.json")
tempFile, _ := os.CreateTemp(t.TempDir(), "*.json")
defer tempFile.Close()
defer os.Remove(tempFile.Name())
tempFile.Write(tt.policyBytes)
@ -462,7 +462,7 @@ func TestOptionsFromViper(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempFile, _ := os.CreateTemp("", "*.json")
tempFile, _ := os.CreateTemp(t.TempDir(), "*.json")
defer tempFile.Close()
defer os.Remove(tempFile.Name())
tempFile.Write(tt.configBytes)
@ -506,8 +506,7 @@ func Test_NewOptionsFromConfigEnvVar(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.envKeyPairs {
os.Setenv(k, v)
defer os.Unsetenv(k)
t.Setenv(k, v)
}
_, err := newOptionsFromConfig("")
if (err != nil) != tt.wantErr {
@ -578,7 +577,7 @@ func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
"ok/custom-ca-file": func(t *testing.T) test {
certPEM, err := newCACertPEM()
require.NoError(t, err)
f, err := os.CreateTemp("", "pomerium-test-ca")
f, err := os.CreateTemp(t.TempDir(), "pomerium-test-ca")
require.NoError(t, err)
n, err := f.Write(certPEM)
require.NoError(t, err)
@ -617,8 +616,7 @@ func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
tc := run(t)
t.Run(name, func(t *testing.T) {
for k, v := range tc.envs {
os.Setenv(k, v)
defer os.Unsetenv(k)
t.Setenv(k, v)
}
o, err := newOptionsFromConfig("")
if err != nil {
@ -658,7 +656,6 @@ func TestCertificatesArrayParsing(t *testing.T) {
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
@ -827,7 +824,6 @@ func TestOptions_DefaultURL(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
u, err := tc.f()
@ -892,22 +888,26 @@ func TestOptions_GetAllRouteableGRPCHosts(t *testing.T) {
}
func TestOptions_GetAllRouteableHTTPHosts(t *testing.T) {
p1 := Policy{From: "https://from1.example.com"}
p1.Validate()
p2 := Policy{From: "https://from2.example.com"}
p2.Validate()
p3 := Policy{From: "https://from3.example.com", TLSDownstreamServerName: "from.example.com"}
p3.Validate()
to := WeightedURLs{{URL: url.URL{Scheme: "https", Host: "to.example.com"}}}
p1 := Policy{From: "https://from1.example.com", To: to}
assert.NoError(t, p1.Validate())
p2 := Policy{From: "https://from2.example.com", To: to}
assert.NoError(t, p2.Validate())
p3 := Policy{From: "https://from3.example.com", TLSDownstreamServerName: "from.example.com", To: to}
assert.NoError(t, p3.Validate())
p4 := Policy{From: "https://from4.example.com", MCP: &MCP{}, To: to}
assert.NoError(t, p4.Validate())
opts := &Options{
AuthenticateURLString: "https://authenticate.example.com",
AuthorizeURLString: "https://authorize.example.com",
DataBrokerURLString: "https://databroker.example.com",
Policies: []Policy{p1, p2, p3},
Policies: []Policy{p1, p2, p3, p4},
Services: "all",
}
hosts, err := opts.GetAllRouteableHTTPHosts()
hosts, mcpHosts, err := opts.GetAllRouteableHTTPHosts()
assert.NoError(t, err)
assert.Empty(t, cmp.Diff(mcpHosts, map[string]bool{"from4.example.com:443": true, "from4.example.com": true}))
assert.Equal(t, []string{
"authenticate.example.com",
@ -920,10 +920,14 @@ func TestOptions_GetAllRouteableHTTPHosts(t *testing.T) {
"from2.example.com:443",
"from3.example.com",
"from3.example.com:443",
"from4.example.com",
"from4.example.com:443",
}, hosts)
}
func TestOptions_ApplySettings(t *testing.T) {
t.Parallel()
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second)
defer clearTimeout()
@ -989,6 +993,48 @@ func TestOptions_ApplySettings(t *testing.T) {
})
assert.Equal(t, NewJWTGroupsFilter([]string{"quux", "zulu"}), options.JWTGroupsFilter)
})
t.Run("jwt_issuer_format", func(t *testing.T) {
options := NewDefaultOptions()
assert.Equal(t, JWTIssuerFormatUnset, options.JWTIssuerFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
JwtIssuerFormat: configpb.IssuerFormat_IssuerURI.Enum(),
})
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, JWTIssuerFormatURI, options.JWTIssuerFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
JwtIssuerFormat: configpb.IssuerFormat_IssuerHostOnly.Enum(),
})
assert.Equal(t, JWTIssuerFormatHostOnly, options.JWTIssuerFormat)
})
t.Run("bearer_token_format", func(t *testing.T) {
t.Parallel()
options := NewDefaultOptions()
assert.Nil(t, options.BearerTokenFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
BearerTokenFormat: configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_DEFAULT.Enum(),
})
assert.Equal(t, ptr(BearerTokenFormatDefault), options.BearerTokenFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, ptr(BearerTokenFormatDefault), options.BearerTokenFormat, "should preserve existing bearer token format")
})
t.Run("idp_access_token_allowed_audiences", func(t *testing.T) {
t.Parallel()
options := NewDefaultOptions()
assert.Nil(t, options.IDPAccessTokenAllowedAudiences)
options.ApplySettings(ctx, nil, &configpb.Settings{
IdpAccessTokenAllowedAudiences: &configpb.Settings_StringList{Values: []string{"x", "y", "z"}},
})
assert.Equal(t, ptr([]string{"x", "y", "z"}), options.IDPAccessTokenAllowedAudiences)
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, ptr([]string{"x", "y", "z"}), options.IDPAccessTokenAllowedAudiences,
"should preserve idp access token allowed audiences")
})
}
func TestOptions_GetSetResponseHeaders(t *testing.T) {
@ -1191,7 +1237,6 @@ LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IUUNBUUVFSUdHaDZGbEJlOHl5OWRSSmdtKzM1
0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
}, nil},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@ -1241,7 +1286,6 @@ func TestOptions_GetCookieSameSite(t *testing.T) {
{"none", http.SameSiteNoneMode},
{"UnKnOwN", http.SameSiteDefaultMode},
} {
tc := tc
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
@ -1270,7 +1314,6 @@ func TestOptions_GetCSRFSameSite(t *testing.T) {
{"UnKnOwN", "", csrf.SameSiteDefaultMode},
{"", apple.Name, csrf.SameSiteNoneMode},
} {
tc := tc
t.Run(tc.cookieSameSite, func(t *testing.T) {
t.Parallel()
@ -1748,3 +1791,7 @@ func must[T any](t T, err error) T {
}
return t
}
func ptr[T any](v T) *T {
return &v
}

View file

@ -167,7 +167,7 @@ type Policy struct {
// Possible values:
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
// - "uri": Issuer strings will be a complete URI, including the scheme and ending with a trailing slash.
JWTIssuerFormat string `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
JWTIssuerFormat JWTIssuerFormat `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
// BearerTokenFormat indicates how authorization bearer tokens are interepreted. Possible values:
// - "default": Only Bearer tokens prefixed with Pomerium- will be interpreted by Pomerium
// - "idp_access_token": The Bearer token will be interpreted as an IdP access token.
@ -200,8 +200,16 @@ type Policy struct {
ShowErrorDetails bool `mapstructure:"show_error_details" yaml:"show_error_details" json:"show_error_details"`
Policy *PPLPolicy `mapstructure:"policy" yaml:"policy,omitempty" json:"policy,omitempty"`
DependsOn []string `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
// MCP is an experimental support for Model Context Protocol upstreams
MCP *MCP `mapstructure:"mcp" yaml:"mcp,omitempty" json:"mcp,omitempty"`
}
// MCP is an experimental support for Model Context Protocol upstreams configuration
type MCP struct{}
// RewriteHeader is a policy configuration option to rewrite an HTTP header.
type RewriteHeader struct {
Header string `mapstructure:"header" yaml:"header" json:"header"`
@ -298,6 +306,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
AllowWebsockets: pb.GetAllowWebsockets(),
CORSAllowPreflight: pb.GetCorsAllowPreflight(),
Description: pb.GetDescription(),
DependsOn: pb.GetDependsOn(),
EnableGoogleCloudServerlessAuthentication: pb.GetEnableGoogleCloudServerlessAuthentication(),
From: pb.GetFrom(),
HostPathRegexRewritePattern: pb.GetHostPathRegexRewritePattern(),
@ -309,9 +318,11 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
IDPClientID: pb.GetIdpClientId(),
IDPClientSecret: pb.GetIdpClientSecret(),
JWTGroupsFilter: NewJWTGroupsFilter(pb.JwtGroupsFilter),
JWTIssuerFormat: JWTIssuerFormatFromPB(pb.JwtIssuerFormat),
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(),
LogoURL: pb.GetLogoUrl(),
MCP: MCPFromPB(pb.GetMcp()),
Name: pb.GetName(),
PassIdentityHeaders: pb.PassIdentityHeaders,
Path: pb.GetPath(),
@ -388,13 +399,6 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
p.EnvoyOpts.Name = pb.Name
}
switch pb.GetJwtIssuerFormat() {
case configpb.IssuerFormat_IssuerHostOnly:
p.JWTIssuerFormat = "hostOnly"
case configpb.IssuerFormat_IssuerURI:
p.JWTIssuerFormat = "uri"
}
p.BearerTokenFormat = BearerTokenFormatFromPB(pb.BearerTokenFormat)
for _, rwh := range pb.RewriteResponseHeaders {
@ -462,15 +466,18 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
AllowWebsockets: p.AllowWebsockets,
CorsAllowPreflight: p.CORSAllowPreflight,
Description: p.Description,
DependsOn: p.DependsOn,
EnableGoogleCloudServerlessAuthentication: p.EnableGoogleCloudServerlessAuthentication,
EnvoyOpts: p.EnvoyOpts,
From: p.From,
Id: p.ID,
IdleTimeout: idleTimeout,
JwtGroupsFilter: p.JWTGroupsFilter.ToSlice(),
JwtIssuerFormat: p.JWTIssuerFormat.ToPB(),
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile,
LogoUrl: p.LogoURL,
Mcp: MCPToPB(p.MCP),
Name: p.Name,
PassIdentityHeaders: p.PassIdentityHeaders,
Path: p.Path,
@ -555,13 +562,6 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
pb.LoadBalancingWeights = weights
}
switch p.JWTIssuerFormat {
case "", "hostOnly":
pb.JwtIssuerFormat = configpb.IssuerFormat_IssuerHostOnly
case "uri":
pb.JwtIssuerFormat = configpb.IssuerFormat_IssuerURI
}
pb.BearerTokenFormat = p.BearerTokenFormat.ToPB()
for _, rwh := range p.RewriteResponseHeaders {
@ -698,6 +698,14 @@ func (p *Policy) Validate() error {
p.compiledRegex, _ = regexp.Compile(rawRE)
}
if !p.JWTIssuerFormat.Valid() {
return fmt.Errorf("config: unsupported jwt_issuer_format value %q", p.JWTIssuerFormat)
}
if len(p.DependsOn) > 5 {
return fmt.Errorf("config: depends_on is limited to 5 additional redirect hosts, got %v", p.DependsOn)
}
return nil
}
@ -824,6 +832,11 @@ func (p *Policy) IsForKubernetes() bool {
return p.KubernetesServiceAccountTokenFile != "" || p.KubernetesServiceAccountToken != ""
}
// IsMCP returns true if the route is for the Model Context Protocol upstream server.
func (p *Policy) IsMCP() bool {
return p != nil && p.MCP != nil
}
// IsTCP returns true if the route is for TCP.
func (p *Policy) IsTCP() bool {
return strings.HasPrefix(p.From, "tcp")

View file

@ -56,6 +56,7 @@ func Test_PolicyValidate(t *testing.T) {
{"TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "tcp://one.example.com:5000", "tcp://two.example.com:5000")}, false},
{"mix of TCP and non-TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "https://example.com", "tcp://example.com:5000")}, true},
{"UDP To URLs", Policy{From: "udp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "udp://one.example.com:5000", "udp://two.example.com:5000")}, false},
{"too many depends_on hosts", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), DependsOn: []string{"a", "b", "c", "d", "e", "f"}}, true},
}
for _, tt := range tests {
@ -176,7 +177,6 @@ func Test_PolicyRouteID(t *testing.T) {
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.NoError(t, tt.basePolicy.Validate())
@ -292,6 +292,22 @@ func TestPolicy_FromToPb(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, p.Redirect.HTTPSRedirect, policyFromProto.Redirect.HTTPSRedirect)
})
t.Run("JWT issuer format", func(t *testing.T) {
for f := range knownJWTIssuerFormats {
p := &Policy{
From: "https://pomerium.io",
To: mustParseWeightedURLs(t, "http://localhost"),
JWTIssuerFormat: f,
}
pbPolicy, err := p.ToProto()
require.NoError(t, err)
policyFromPb, err := NewPolicyFromProto(pbPolicy)
assert.NoError(t, err)
assert.Equal(t, f, policyFromPb.JWTIssuerFormat)
}
})
}
func TestPolicy_Matches(t *testing.T) {

View file

@ -14,9 +14,6 @@ var (
// RuntimeFlagGRPCDatabrokerKeepalive enables gRPC keepalive to the databroker service
RuntimeFlagGRPCDatabrokerKeepalive = runtimeFlag("grpc_databroker_keepalive", false)
// RuntimeFlagLegacyIdentityManager enables the legacy identity manager
RuntimeFlagLegacyIdentityManager = runtimeFlag("legacy_identity_manager", false)
// RuntimeFlagMatchAnyIncomingPort enables ignoring the incoming port when matching routes
RuntimeFlagMatchAnyIncomingPort = runtimeFlag("match_any_incoming_port", true)
@ -25,6 +22,13 @@ var (
// is deprecated pending removal in a future release, but this flag allows a temporary
// opt-out from the deprecation.
RuntimeFlagPomeriumJWTEndpoint = runtimeFlag("pomerium_jwt_endpoint", false)
// RuntimeFlagAddExtraMetricsLabels enables adding extra labels to metrics (host and installation id)
RuntimeFlagAddExtraMetricsLabels = runtimeFlag("add_extra_metrics_labels", true)
// RuntimeFlagAuthorizeUseSyncedData enables synced data for querying the databroker for
// certain types of data.
RuntimeFlagAuthorizeUseSyncedData = runtimeFlag("authorize_use_synced_data", true)
)
// RuntimeFlag is a runtime flag that can flip on/off certain features

View file

@ -202,7 +202,7 @@ func (c *incomingIDPTokenSessionCreator) createSessionAccessToken(
if err != nil {
return nil, fmt.Errorf("error verifying access token: %w", err)
} else if !res.Valid {
return nil, fmt.Errorf("invalid access token")
return nil, fmt.Errorf("%w: invalid access token", sessions.ErrInvalidSession)
}
s = c.newSessionFromIDPClaims(cfg, sessionID, res.Claims)
@ -265,7 +265,7 @@ func (c *incomingIDPTokenSessionCreator) createSessionForIdentityToken(
if err != nil {
return nil, fmt.Errorf("error verifying identity token: %w", err)
} else if !res.Valid {
return nil, fmt.Errorf("invalid identity token")
return nil, fmt.Errorf("%w: invalid identity token", sessions.ErrInvalidSession)
}
s = c.newSessionFromIDPClaims(cfg, sessionID, res.Claims)
@ -405,22 +405,8 @@ func (cfg *Config) GetIncomingIDPAccessTokenForPolicy(policy *Policy, r *http.Re
bearerTokenFormat = *policy.BearerTokenFormat
}
if token := r.Header.Get(httputil.HeaderPomeriumIDPAccessToken); token != "" {
return token, true
}
if auth := r.Header.Get(httputil.HeaderAuthorization); auth != "" {
prefix := httputil.AuthorizationTypePomeriumIDPAccessToken + " "
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
return auth[len(prefix):], true
}
prefix = "Bearer " + httputil.AuthorizationTypePomeriumIDPAccessToken + "-"
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
return auth[len(prefix):], true
}
prefix = "Bearer "
prefix := "Bearer "
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) &&
bearerTokenFormat == BearerTokenFormatIDPAccessToken {
return auth[len(prefix):], true
@ -440,22 +426,8 @@ func (cfg *Config) GetIncomingIDPIdentityTokenForPolicy(policy *Policy, r *http.
bearerTokenFormat = *policy.BearerTokenFormat
}
if token := r.Header.Get(httputil.HeaderPomeriumIDPIdentityToken); token != "" {
return token, true
}
if auth := r.Header.Get(httputil.HeaderAuthorization); auth != "" {
prefix := httputil.AuthorizationTypePomeriumIDPIdentityToken + " "
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
return auth[len(prefix):], true
}
prefix = "Bearer " + httputil.AuthorizationTypePomeriumIDPIdentityToken + "-"
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
return auth[len(prefix):], true
}
prefix = "Bearer "
prefix := "Bearer "
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) &&
bearerTokenFormat == BearerTokenFormatIDPIdentityToken {
return auth[len(prefix):], true

View file

@ -206,24 +206,6 @@ func TestGetIncomingIDPAccessTokenForPolicy(t *testing.T) {
name: "empty headers",
expectedOK: false,
},
{
name: "custom header",
headers: http.Header{"X-Pomerium-Idp-Access-Token": {"access token via custom header"}},
expectedOK: true,
expectedToken: "access token via custom header",
},
{
name: "custom authorization",
headers: http.Header{"Authorization": {"Pomerium-Idp-Access-Token access token via custom authorization"}},
expectedOK: true,
expectedToken: "access token via custom authorization",
},
{
name: "custom bearer",
headers: http.Header{"Authorization": {"Bearer Pomerium-Idp-Access-Token-access token via custom bearer"}},
expectedOK: true,
expectedToken: "access token via custom bearer",
},
{
name: "bearer disabled",
headers: http.Header{"Authorization": {"Bearer access token via bearer"}},
@ -289,24 +271,6 @@ func TestGetIncomingIDPIdentityTokenForPolicy(t *testing.T) {
name: "empty headers",
expectedOK: false,
},
{
name: "custom header",
headers: http.Header{"X-Pomerium-Idp-Identity-Token": {"identity token via custom header"}},
expectedOK: true,
expectedToken: "identity token via custom header",
},
{
name: "custom authorization",
headers: http.Header{"Authorization": {"Pomerium-Idp-Identity-Token identity token via custom authorization"}},
expectedOK: true,
expectedToken: "identity token via custom authorization",
},
{
name: "custom bearer",
headers: http.Header{"Authorization": {"Bearer Pomerium-Idp-Identity-Token-identity token via custom bearer"}},
expectedOK: true,
expectedToken: "identity token via custom bearer",
},
{
name: "bearer disabled",
headers: http.Header{"Authorization": {"Bearer identity token via bearer"}},
@ -496,12 +460,14 @@ func TestIncomingIDPTokenSessionCreator_CreateSession(t *testing.T) {
cfg.Options.AuthenticateURLString = srv.URL
cfg.Options.ClientSecret = "CLIENT_SECRET_1"
cfg.Options.ClientID = "CLIENT_ID_1"
bearerTokenFormatIDPAccessToken := BearerTokenFormatIDPAccessToken
cfg.Options.BearerTokenFormat = &bearerTokenFormatIDPAccessToken
route := &Policy{}
route.IDPClientSecret = "CLIENT_SECRET_2"
route.IDPClientID = "CLIENT_ID_2"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.example.com", nil)
require.NoError(t, err)
req.Header.Set(httputil.HeaderPomeriumIDPAccessToken, "ACCESS_TOKEN")
req.Header.Set("Authorization", "Bearer ACCESS_TOKEN")
c := NewIncomingIDPTokenSessionCreator(
func(_ context.Context, _, _ string) (*databroker.Record, error) {
return nil, storage.ErrNotFound
@ -537,12 +503,14 @@ func TestIncomingIDPTokenSessionCreator_CreateSession(t *testing.T) {
cfg.Options.AuthenticateURLString = srv.URL
cfg.Options.ClientSecret = "CLIENT_SECRET_1"
cfg.Options.ClientID = "CLIENT_ID_1"
bearerTokenFormatIDPIdentityToken := BearerTokenFormatIDPIdentityToken
cfg.Options.BearerTokenFormat = &bearerTokenFormatIDPIdentityToken
route := &Policy{}
route.IDPClientSecret = "CLIENT_SECRET_2"
route.IDPClientID = "CLIENT_ID_2"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.example.com", nil)
require.NoError(t, err)
req.Header.Set(httputil.HeaderPomeriumIDPIdentityToken, "IDENTITY_TOKEN")
req.Header.Set("Authorization", "Bearer IDENTITY_TOKEN")
c := NewIncomingIDPTokenSessionCreator(
func(_ context.Context, _, _ string) (*databroker.Record, error) {
return nil, storage.ErrNotFound

View file

@ -11,6 +11,7 @@ import (
"github.com/rs/zerolog"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
oteltrace "go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
@ -19,7 +20,6 @@ import (
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/events"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/envoy/files"
@ -27,9 +27,8 @@ import (
"github.com/pomerium/pomerium/pkg/grpc/registry"
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/legacymanager"
"github.com/pomerium/pomerium/pkg/identity/manager"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// DataBroker represents the databroker service. The databroker service is a simple interface
@ -38,7 +37,6 @@ type DataBroker struct {
Options
dataBrokerServer *dataBrokerServer
manager *manager.Manager
legacyManager *legacymanager.Manager
eventsMgr *events.Manager
localListener net.Listener
@ -50,8 +48,7 @@ type DataBroker struct {
}
type Options struct {
managerOptions []manager.Option
legacyManagerOptions []legacymanager.Option
managerOptions []manager.Option
}
type Option func(*Options)
@ -68,12 +65,6 @@ func WithManagerOptions(managerOptions ...manager.Option) Option {
}
}
func WithLegacyManagerOptions(legacyManagerOptions ...legacymanager.Option) Option {
return func(o *Options) {
o.legacyManagerOptions = append(o.legacyManagerOptions, legacyManagerOptions...)
}
}
// New creates a new databroker service.
func New(ctx context.Context, cfg *config.Config, eventsMgr *events.Manager, opts ...Option) (*DataBroker, error) {
options := Options{}
@ -115,7 +106,7 @@ func New(ctx context.Context, cfg *config.Config, eventsMgr *events.Manager, opt
}
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("service", "databroker").Str("config_source", "bootstrap")
return c.Str("service", "databroker").Str("config-source", "bootstrap")
})
localGRPCConnection, err := grpc.DialContext(
ctx,
@ -201,13 +192,7 @@ func (c *DataBroker) update(ctx context.Context, cfg *config.Config) error {
options := append([]manager.Option{
manager.WithDataBrokerClient(dataBrokerClient),
manager.WithEventManager(c.eventsMgr),
manager.WithEnabled(!cfg.Options.IsRuntimeFlagSet(config.RuntimeFlagLegacyIdentityManager)),
}, c.managerOptions...)
legacyOptions := append([]legacymanager.Option{
legacymanager.WithDataBrokerClient(dataBrokerClient),
legacymanager.WithEventManager(c.eventsMgr),
legacymanager.WithEnabled(cfg.Options.IsRuntimeFlagSet(config.RuntimeFlagLegacyIdentityManager)),
}, c.legacyManagerOptions...)
if cfg.Options.SupportsUserRefresh() {
authenticator, err := identity.NewAuthenticator(ctx, c.tracerProvider, oauthOptions)
@ -215,7 +200,6 @@ func (c *DataBroker) update(ctx context.Context, cfg *config.Config) error {
log.Ctx(ctx).Error().Err(err).Msg("databroker: failed to create authenticator")
} else {
options = append(options, manager.WithAuthenticator(authenticator))
legacyOptions = append(legacyOptions, legacymanager.WithAuthenticator(authenticator))
}
} else {
log.Ctx(ctx).Info().Msg("databroker: disabling refresh of user sessions")
@ -227,12 +211,6 @@ func (c *DataBroker) update(ctx context.Context, cfg *config.Config) error {
c.manager.UpdateConfig(options...)
}
if c.legacyManager == nil {
c.legacyManager = legacymanager.New(legacyOptions...)
} else {
c.legacyManager.UpdateConfig(legacyOptions...)
}
return nil
}

168
go.mod
View file

@ -1,29 +1,31 @@
module github.com/pomerium/pomerium
go 1.23.6
toolchain go1.23.7
go 1.23.8
require (
cloud.google.com/go/storage v1.50.0
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1
cloud.google.com/go/storage v1.51.0
contrib.go.opencensus.io/exporter/prometheus v0.4.2
github.com/CAFxX/httpcompression v0.0.9
github.com/VictoriaMetrics/fastcache v1.12.2
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.8
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.0
github.com/bits-and-blooms/bitset v1.21.0
github.com/caddyserver/certmagic v0.21.7
github.com/aws/aws-sdk-go-v2/config v1.29.12
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.0
github.com/bits-and-blooms/bitset v1.22.0
github.com/bufbuild/protovalidate-go v0.9.3
github.com/caddyserver/certmagic v0.22.2
github.com/cenkalti/backoff/v4 v4.3.0
github.com/cloudflare/circl v1.6.0
github.com/coreos/go-oidc/v3 v3.12.0
github.com/docker/docker v28.0.1+incompatible
github.com/coreos/go-oidc/v3 v3.13.0
github.com/docker/docker v28.0.4+incompatible
github.com/envoyproxy/go-control-plane/envoy v1.32.4
github.com/envoyproxy/protoc-gen-validate v1.2.1
github.com/exaring/otelpgx v0.9.0
github.com/fsnotify/fsnotify v1.8.0
github.com/gaissmai/bart v0.20.3
github.com/go-chi/chi/v5 v5.2.1
github.com/go-jose/go-jose/v3 v3.0.4
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/google/btree v1.1.3
github.com/google/go-cmp v0.7.0
github.com/google/go-jsonnet v0.20.0
@ -31,21 +33,21 @@ require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-set/v3 v3.0.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jackc/pgx/v5 v5.7.2
github.com/jackc/pgx/v5 v5.7.4
github.com/jxskiss/base62 v1.1.0
github.com/klauspost/compress v1.18.0
github.com/martinlindhe/base36 v1.1.1
github.com/mholt/acmez/v3 v3.0.1
github.com/minio/minio-go/v7 v7.0.87
github.com/mholt/acmez/v3 v3.1.1
github.com/minio/minio-go/v7 v7.0.89
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
github.com/natefinch/atomic v1.0.1
github.com/oapi-codegen/runtime v1.1.1
github.com/open-policy-agent/opa v1.2.0
github.com/open-policy-agent/opa v1.3.0
github.com/peterbourgon/ff/v3 v3.4.0
github.com/pires/go-proxyproto v0.8.0
github.com/pomerium/csrf v1.7.0
@ -53,91 +55,92 @@ require (
github.com/pomerium/envoy-custom v1.33.0
github.com/pomerium/protoutil v0.0.0-20240813175624-47b7ac43ff46
github.com/pomerium/webauthn v0.0.0-20240603205124-0428df511172
github.com/prometheus/client_golang v1.21.0
github.com/prometheus/client_golang v1.21.1
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.62.0
github.com/prometheus/procfs v0.15.1
github.com/quic-go/quic-go v0.50.0
github.com/prometheus/common v0.63.0
github.com/prometheus/procfs v0.16.0
github.com/quic-go/quic-go v0.50.1
github.com/rs/cors v1.11.1
github.com/rs/zerolog v1.33.0
github.com/rs/zerolog v1.34.0
github.com/shirou/gopsutil/v3 v3.24.5
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.19.0
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.35.0
github.com/testcontainers/testcontainers-go v0.36.0
github.com/tidwall/gjson v1.18.0
github.com/tniswong/go.rfcx v0.0.0-20181019234604-07783c52761f
github.com/volatiletech/null/v9 v9.0.0
github.com/yuin/gopher-lua v1.1.1
github.com/zeebo/xxh3 v1.0.2
go.opencensus.io v0.24.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0
go.opentelemetry.io/contrib/propagators/autoprop v0.59.0
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/bridge/opencensus v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0
go.opentelemetry.io/otel/metric v1.34.0
go.opentelemetry.io/otel/sdk v1.34.0
go.opentelemetry.io/otel/sdk/metric v1.34.0
go.opentelemetry.io/otel/trace v1.34.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
go.opentelemetry.io/contrib/propagators/autoprop v0.60.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/bridge/opencensus v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0
go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/sdk/metric v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.opentelemetry.io/proto/otlp v1.5.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/mock v0.5.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.33.0
golang.org/x/net v0.35.0
golang.org/x/oauth2 v0.27.0
golang.org/x/sync v0.11.0
golang.org/x/sys v0.30.0
golang.org/x/time v0.10.0
google.golang.org/api v0.223.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5
golang.org/x/crypto v0.36.0
golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/time v0.11.0
google.golang.org/api v0.224.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
sigs.k8s.io/yaml v1.4.0
)
require (
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go v0.116.0 // indirect
cel.dev/expr v0.19.2 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect
dario.cat/mergo v1.0.0 // indirect
cloud.google.com/go/iam v1.4.1 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.61 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.16 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
@ -146,6 +149,7 @@ require (
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
@ -161,27 +165,27 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.24.1 // indirect
github.com/google/go-tpm v0.9.0 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/libdns/libdns v0.2.3 // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
@ -195,8 +199,8 @@ require (
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/ginkgo/v2 v2.19.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@ -206,15 +210,16 @@ require (
github.com/quic-go/qpack v0.5.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
@ -230,19 +235,18 @@ require (
github.com/zeebo/assert v1.3.1 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.34.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.35.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.35.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

358
go.sum
View file

@ -1,5 +1,7 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 h1:zgJPqo17m28+Lf5BW4xv3PvU20BnrmTcGYrog22lLIU=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@ -15,8 +17,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
@ -31,14 +33,14 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM=
cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=
cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -48,14 +50,14 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs=
cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw=
cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc=
cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8=
contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg=
contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
@ -67,12 +69,12 @@ github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWG
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
@ -90,6 +92,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
@ -98,10 +102,10 @@ github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38y
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.8 h1:RpwAfYcV2lr/yRc4lWhUM9JRPQqKgKWmou3LV7UfWP4=
github.com/aws/aws-sdk-go-v2/config v1.29.8/go.mod h1:t+G7Fq1OcO8cXTPPXzxQSnj/5Xzdc9jAAD3Xrn9/Mgo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.61 h1:Hd/uX6Wo2iUW1JWII+rmyCD7MMhOe7ALwQXN6sKDd1o=
github.com/aws/aws-sdk-go-v2/credentials v1.17.61/go.mod h1:L7vaLkwHY1qgW0gG1zG0z/X0sQ5tpIY5iI13+j3qI80=
github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo=
github.com/aws/aws-sdk-go-v2/config v1.29.12/go.mod h1:xse1YTjmORlb/6fhkWi8qJh3cvZi4JoVNhc+NbJt4kI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 h1:q+nV2yYegofO/SUXruT+pn4KxkxmaQ++1B/QedcKBFM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65/go.mod h1:4zyjAuGOdikpNYiSGpsGz8hLGmUzlY8pc8r9QQ/RXYQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
@ -114,33 +118,35 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcu
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 h1:t/gZFyrijKuSU0elA5kRngP/oU3mc0I+Dvp8HwRE4c0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.0 h1:EBm8lXevBWe+kK9VOU/IBeOI189WPRwPUc3LvJK9GOs=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.0/go.mod h1:4qzsZSzB/KiX2EzDjs9D7A8rI/WGJxZceVJIHqtJjIU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.0 h1:2U9sF8nKy7UgyEeLiZTRg6ShBS22z8UnYpV6aRFL0is=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.0/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.0 h1:wjAdc85cXdQR5uLx5FwWvGIHm4OPJhTyzUHU8craXtE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.0/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.16 h1:BHEK2Q/7CMRMCb3nySi/w8UbIcPhKvYP5s1xf8/izn0=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.16/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.0 h1:OIw2nryEApESTYI5deCZGcq4Gvz8DBAt4tJlNyg3v5o=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.0/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.21.0 h1:9RlxRbMI5dRNNburKqfDSiz5POfImKgtablyV01WUw0=
github.com/bits-and-blooms/bitset v1.21.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bufbuild/protovalidate-go v0.9.3 h1:XvdtwQuppS3wjzGfpOirsqwN5ExH2+PiIuA/XZd3MTM=
github.com/bufbuild/protovalidate-go v0.9.3/go.mod h1:2lUDP6fNd3wxznRNH3Nj64VB07+PySeslamkerwP6tE=
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.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI=
github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk=
github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@ -158,14 +164,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q=
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
@ -176,22 +182,24 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps=
github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA=
github.com/dgraph-io/badger/v4 v4.6.0 h1:acOwfOOZ4p1dPRnYzvkVm7rUk2Y21TgPVepCy5dJdFQ=
github.com/dgraph-io/badger/v4 v4.6.0/go.mod h1:KSJ5VTuZNC3Sd+YhvVjk2nYua9UZnnTr/SkXvdtiPgI=
github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I=
github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -220,6 +228,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.20.3 h1:hZxPDasx5f2rmNsKwvNu5JMV0+SUs/uovkUNKN/807U=
github.com/gaissmai/bart v0.20.3/go.mod h1:HRCXF6EPBV4dcRPUTZtjVx384e3RYVHJ5H22ApAqltA=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -255,6 +265,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
@ -303,8 +315,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8=
github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -345,8 +359,8 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g=
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
@ -359,10 +373,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 h1:KcFzXwzM/kGhIRHvc8jdixfIJjVzuUJdnv+5xsPutog=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -374,8 +388,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -384,8 +396,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@ -406,8 +418,8 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
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=
@ -423,12 +435,12 @@ github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c h1
github.com/kralicky/go-adaptive-radix-tree v0.0.0-20240624235931-330eb762e74c/go.mod h1:oJwexVSshEat0E3evyKOH6QzN8GFWrhLvEoh8GiJzss=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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=
@ -438,16 +450,16 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8=
github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ=
github.com/mholt/acmez/v3 v3.1.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ=
github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
github.com/minio/minio-go/v7 v7.0.89 h1:hx4xV5wwTUfyv8LarhJAwNecnXpoTsj9v3f3q/ZkiJU=
github.com/minio/minio-go/v7 v7.0.89/go.mod h1:2rFnGAp02p7Dddo1Fq4S2wYOfpF0MUTSeLTRC90I204=
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=
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
@ -492,14 +504,14 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/open-policy-agent/opa v1.2.0 h1:88NDVCM0of1eO6Z4AFeL3utTEtMuwloFmWWU7dRV1z0=
github.com/open-policy-agent/opa v1.2.0/go.mod h1:30euUmOvuBoebRCcJ7DMF42bRBOPznvt0ACUMYDUGVY=
github.com/open-policy-agent/opa v1.3.0 h1:zVvQvQg+9+FuSRBt4LgKNzJwsWl/c85kD5jPozJTydY=
github.com/open-policy-agent/opa v1.3.0/go.mod h1:t9iPNhaplD2qpiBqeudzJtEX3fKHK8zdA29oFvofAHo=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
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/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@ -537,8 +549,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -551,22 +563,22 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0=
github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -574,20 +586,19 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ=
@ -599,19 +610,21 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4 h1:/jKH9ivHOUkahZs3zPfJfOmkXDFB6OdsHZ4W8gyDb/c=
github.com/sryoya/protorand v0.0.0-20240429201223-e7440656b2a4/go.mod h1:9a23nlv6vzBeVlQq6JQCjljZ6sfzsB6aha1m5Ly1W2Y=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -628,7 +641,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
@ -636,8 +648,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00=
github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -690,44 +702,44 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/contrib/propagators/autoprop v0.59.0 h1:bgG6F0HBLngIG79m8VYMdgh3adfcjCgLbsO8StsovQk=
go.opentelemetry.io/contrib/propagators/autoprop v0.59.0/go.mod h1:JV4DSHIsqQoFVaeE6xDef6xdI2I/IOOsWnXWCzQ0EXQ=
go.opentelemetry.io/contrib/propagators/aws v1.34.0 h1:pv/Yi44N2BM1Kyl6wxO6bTiwcxUA7Deog3Rc7NO9ITE=
go.opentelemetry.io/contrib/propagators/aws v1.34.0/go.mod h1:1aF3HFtAyIi+B2xJHOdKQcNz+bcDS+JLAZjsohcW1P4=
go.opentelemetry.io/contrib/propagators/b3 v1.34.0 h1:9pQdCEvV/6RWQmag94D6rhU+A4rzUhYBEJ8bpscx5p8=
go.opentelemetry.io/contrib/propagators/b3 v1.34.0/go.mod h1:FwM71WS8i1/mAK4n48t0KU6qUS/OZRBgDrHZv3RlJ+w=
go.opentelemetry.io/contrib/propagators/jaeger v1.34.0 h1:D3htJISCUU/wOVlKwisVKancWm+2U4h9xDEaiMkiyRE=
go.opentelemetry.io/contrib/propagators/jaeger v1.34.0/go.mod h1:DAX1bsj+uDm2ZuOQH/RgZRx7RQZWyzV5W2WR/0UX8JA=
go.opentelemetry.io/contrib/propagators/ot v1.34.0 h1:fcA0FMvHmco/mjXbmRoNx9IRs6+UuRVyXhMHzP+pEc8=
go.opentelemetry.io/contrib/propagators/ot v1.34.0/go.mod h1:VvybuUSU0G7m9DLza4YX8KKniYlIphI+mZ6ufkaV1m8=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/bridge/opencensus v1.34.0 h1:2Uxf3WAnOkGFTMlMShbiHNF2qN1iGdnt5m6hUnUp07k=
go.opentelemetry.io/otel/bridge/opencensus v1.34.0/go.mod h1:ALZT48QF8vj9XiFlBFuBGBQsj9Wk8Sk1zdyu6/1MCVs=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/propagators/autoprop v0.60.0 h1:sevByeAWTtfBdJQT7nkJfK5wOCjNpmDMZGPEBx3l1RA=
go.opentelemetry.io/contrib/propagators/autoprop v0.60.0/go.mod h1:uEhyRPnUTSeUwMjDdrMQnsJ0sQ2mf/fA94hfchemm4A=
go.opentelemetry.io/contrib/propagators/aws v1.35.0 h1:xoXA+5dVwsf5uE5GvSJ3lKiapyMFuIzbEmJwQ0JP+QU=
go.opentelemetry.io/contrib/propagators/aws v1.35.0/go.mod h1:s11Orts/IzEgw9Srw5iRXtk2kM2j3jt/45noUWyf60E=
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ=
go.opentelemetry.io/contrib/propagators/b3 v1.35.0/go.mod h1:9+SNxwqvCWo1qQwUpACBY5YKNVxFJn5mlbXg/4+uKBg=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 h1:UIrZgRBHUrYRlJ4V419lVb4rs2ar0wFzKNAebaP05XU=
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0/go.mod h1:0ciyFyYZxE6JqRAQvIgGRabKWDUmNdW3GAQb6y/RlFU=
go.opentelemetry.io/contrib/propagators/ot v1.35.0 h1:ZsgYijVvOpju4mq3g4QyqCwLKs2vKenlCpZHbKu50OA=
go.opentelemetry.io/contrib/propagators/ot v1.35.0/go.mod h1:t1ZwtgjEtDH9uW6OlCRVLL2wOgsTJmp0pJwNouUq+HE=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/bridge/opencensus v1.35.0 h1:4nJfffRbozhqnuukfRkiahA94mnpryCLJLiduMIDJKI=
go.opentelemetry.io/otel/bridge/opencensus v1.35.0/go.mod h1:359S30saRYNsB4A46EDx91SpXsQFNgkma7ftg2/L5/M=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
@ -750,8 +762,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -786,8 +798,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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=
@ -827,8 +839,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -836,8 +848,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -851,8 +863,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -912,15 +924,15 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
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=
@ -931,13 +943,13 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -983,8 +995,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
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=
@ -1005,8 +1017,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU=
google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1042,12 +1054,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1061,8 +1073,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
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=
@ -1077,8 +1089,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/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.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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=
@ -1087,8 +1099,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
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=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -63,9 +63,7 @@ func TestHealth(t *testing.T) {
endpoints := []string{"healthz", "ping"}
for _, route := range pomeriumRoutes {
route := route
for _, endpoint := range endpoints {
endpoint := endpoint
routeToCheck := fmt.Sprintf("%s/%s", route, endpoint)
t.Run(routeToCheck, func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, routeToCheck, nil)

View file

@ -41,6 +41,6 @@ func (v *Value[T]) Swap(val T) T {
}
// Swap swaps the value atomically.
func (v *Value[T]) CompareAndSwap(old, new T) bool {
return v.value.CompareAndSwap(old, new)
func (v *Value[T]) CompareAndSwap(old, n T) bool {
return v.value.CompareAndSwap(old, n)
}

View file

@ -14,10 +14,10 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/grpc"
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// timeNow is time.Now but pulled out as a variable for tests.

View file

@ -0,0 +1,98 @@
package authenticateflow_test
import (
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/scenarios"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/upstreams"
"github.com/pomerium/pomerium/internal/testenv/values"
)
func newHTTPUpstream(
env testenv.Environment, subdomain string,
) (upstreams.HTTPUpstream, testenv.Route) {
up := upstreams.HTTP(nil)
up.Handle("/", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "hello world") })
route := up.Route().
From(env.SubdomainURL(subdomain)).
To(values.Bind(up.Addr(), func(addr string) string {
// override the target protocol to use http://
return fmt.Sprintf("http://%s", addr)
})).
Policy(func(p *config.Policy) { p.AllowAnyAuthenticatedUser = true })
env.AddUpstream(up)
return up, route
}
func TestMultiDomainLogin(t *testing.T) {
env := testenv.New(t)
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
// Create three routes to be linked via multi-domain login...
upstreamA, routeA := newHTTPUpstream(env, "a")
upstreamB, routeB := newHTTPUpstream(env, "b")
upstreamC, routeC := newHTTPUpstream(env, "c")
// ...and one route that will not be involved.
upstreamD, routeD := newHTTPUpstream(env, "d")
// Configure route A to redirect through routes B and C on login.
routeA.Policy(func(p *config.Policy) {
p.DependsOn = []string{
strings.TrimPrefix(routeB.URL().Value(), "https://"),
strings.TrimPrefix(routeC.URL().Value(), "https://"),
}
})
env.Start()
snippets.WaitStartupComplete(env)
// By default the testenv code will use a separate http.Client for each
// separate route. Instead we specifically want to test the cross-route
// behavior for a single client.
cj, err := cookiejar.New(nil)
require.NoError(t, err)
sharedClient := http.Client{Jar: cj}
withSharedClient := upstreams.ClientHook(
func(_ *http.Client) *http.Client { return &sharedClient })
assertResponseOK := func(resp *http.Response, err error) {
t.Helper()
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
io.ReadAll(resp.Body)
resp.Body.Close()
}
assertRedirect := func(resp *http.Response, err error) {
t.Helper()
require.NoError(t, err)
assert.Equal(t, http.StatusFound, resp.StatusCode)
io.ReadAll(resp.Body)
resp.Body.Close()
}
// Log in to the first route.
assertResponseOK(upstreamA.Get(routeA, withSharedClient, upstreams.AuthenticateAs("test@example.com")))
// The client should also be authenticated for routes B and C without any
// additional login redirects.
sharedClient.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
}
assertResponseOK(upstreamB.Get(routeB, withSharedClient))
assertResponseOK(upstreamC.Get(routeC, withSharedClient))
// The client should not be authenticated for route D.
assertRedirect(upstreamD.Get(routeD, withSharedClient))
}

View file

@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
@ -23,7 +24,6 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -33,6 +33,7 @@ import (
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/manager"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Stateful implements the stateful authentication flow. In this flow, the
@ -163,7 +164,9 @@ func (s *Stateful) SignIn(
// base64 our encrypted payload for URL-friendlyness
encodedJWT := base64.URLEncoding.EncodeToString(encryptedJWT)
callbackURL, err := urlutil.GetCallbackURL(r, encodedJWT)
additionalHosts := strings.Split(r.FormValue(urlutil.QueryAdditionalHosts), ",")
callbackURL, err := urlutil.GetCallbackURL(r, encodedJWT, additionalHosts)
if err != nil {
return httputil.NewError(http.StatusBadRequest, err)
}
@ -324,7 +327,11 @@ func (s *Stateful) LogAuthenticateEvent(*http.Request) {}
// AuthenticateSignInURL returns a URL to redirect the user to the authenticate
// domain.
func (s *Stateful) AuthenticateSignInURL(
ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string,
ctx context.Context,
queryParams url.Values,
redirectURL *url.URL,
idpID string,
additionalHosts []string,
) (string, error) {
signinURL := s.authenticateURL.ResolveReference(&url.URL{
Path: "/.pomerium/sign_in",
@ -335,6 +342,9 @@ func (s *Stateful) AuthenticateSignInURL(
}
queryParams.Set(urlutil.QueryRedirectURI, redirectURL.String())
queryParams.Set(urlutil.QueryIdentityProviderID, idpID)
if len(additionalHosts) > 0 {
queryParams.Set(urlutil.QueryAdditionalHosts, strings.Join(additionalHosts, ","))
}
otel.GetTextMapPropagator().Inject(ctx, trace.PomeriumURLQueryCarrier(queryParams))
signinURL.RawQuery = queryParams.Encode()
redirectTo := urlutil.NewSignedURL(s.sharedKey, signinURL).String()
@ -387,6 +397,23 @@ func (s *Stateful) Callback(w http.ResponseWriter, r *http.Request) error {
redirectURL.RawQuery = q.Encode()
}
// Redirect chaining for multi-domain login.
additionalHosts := r.URL.Query().Get(urlutil.QueryAdditionalHosts)
if additionalHosts != "" {
nextHops := strings.Split(additionalHosts, ",")
log.Ctx(r.Context()).Debug().Strs("next-hops", nextHops).Msg("multi-domain login callback")
callbackURL, err := urlutil.GetCallbackURL(r, encryptedSession, nextHops[1:])
if err != nil {
return httputil.NewError(http.StatusInternalServerError,
fmt.Errorf("proxy: couldn't get next hop callback URL: %w", err))
}
callbackURL.Host = nextHops[0]
signedCallbackURL := urlutil.NewSignedURL(s.sharedKey, callbackURL)
httputil.Redirect(w, r, signedCallbackURL.String(), http.StatusFound)
return nil
}
// redirect
httputil.Redirect(w, r, redirectURL.String(), http.StatusFound)
return nil

View file

@ -67,7 +67,6 @@ func TestStatefulSignIn(t *testing.T) {
{"good programmatic request", "corp.example.example", map[string]string{urlutil.QueryIsProgrammatic: "true", urlutil.QueryRedirectURI: "https://dst.some.example/"}, true, &sessions.State{}, &mock.Encoder{}, nil, "", "https://dst.some.example/.pomerium/callback/"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sessionStore := &mstore.Store{SaveError: tt.saveError}
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, sessionStore)
@ -129,7 +128,7 @@ func TestStatefulAuthenticateSignInURL(t *testing.T) {
t.Run("NilQueryParams", func(t *testing.T) {
redirectURL := &url.URL{Scheme: "https", Host: "example.com"}
u, err := flow.AuthenticateSignInURL(context.Background(), nil, redirectURL, "fake-idp-id")
u, err := flow.AuthenticateSignInURL(context.Background(), nil, redirectURL, "fake-idp-id", nil)
assert.NoError(t, err)
parsed, _ := url.Parse(u)
assert.NoError(t, urlutil.NewSignedURL(key, parsed).Validate())
@ -144,7 +143,7 @@ func TestStatefulAuthenticateSignInURL(t *testing.T) {
redirectURL := &url.URL{Scheme: "https", Host: "example.com"}
q := url.Values{}
q.Set("foo", "bar")
u, err := flow.AuthenticateSignInURL(context.Background(), q, redirectURL, "fake-idp-id")
u, err := flow.AuthenticateSignInURL(context.Background(), q, redirectURL, "fake-idp-id", nil)
assert.NoError(t, err)
parsed, _ := url.Parse(u)
assert.NoError(t, urlutil.NewSignedURL(key, parsed).Validate())
@ -156,6 +155,21 @@ func TestStatefulAuthenticateSignInURL(t *testing.T) {
assert.Equal(t, "fake-idp-id", q.Get("pomerium_idp_id"))
assert.Equal(t, "bar", q.Get("foo"))
})
t.Run("AdditionalHosts", func(t *testing.T) {
redirectURL := &url.URL{Scheme: "https", Host: "example.com"}
additionalHosts := []string{"foo.example.com", "bar.example.com:1234"}
u, err := flow.AuthenticateSignInURL(context.Background(), nil, redirectURL, "fake-idp-id", additionalHosts)
assert.NoError(t, err)
parsed, _ := url.Parse(u)
assert.NoError(t, urlutil.NewSignedURL(key, parsed).Validate())
assert.Equal(t, "https", parsed.Scheme)
assert.Equal(t, "authenticate.example.com", parsed.Host)
assert.Equal(t, "/.pomerium/sign_in", parsed.Path)
q := parsed.Query()
assert.Equal(t, "https://example.com", parsed.Query().Get("pomerium_redirect_uri"))
assert.Equal(t, "fake-idp-id", q.Get("pomerium_idp_id"))
assert.Equal(t, "foo.example.com,bar.example.com:1234", q.Get("pomerium_additional_hosts"))
})
}
func TestStatefulGetIdentityProviderIDForURLValues(t *testing.T) {
@ -278,6 +292,7 @@ func TestStatefulCallback(t *testing.T) {
}
location, _ := url.Parse(w.Result().Header.Get("Location"))
assert.Equal(t, "example.com", location.Host)
assert.Equal(t, "/", location.Path)
assert.Equal(t, "ok", location.Query().Get("pomerium_callback_uri"))
} else {
if err == nil || !strings.Contains(err.Error(), tt.wantErrorMsg) {
@ -288,6 +303,60 @@ func TestStatefulCallback(t *testing.T) {
}
}
func TestStatefulCallback_AdditionalHosts(t *testing.T) {
opts := config.NewDefaultOptions()
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
sharedKey, _ := opts.GetSharedKey()
flow, err := NewStateful(
context.Background(),
trace.NewNoopTracerProvider(),
&config.Config{Options: opts},
&mstore.Store{Session: &sessions.State{}},
)
require.NoError(t, err)
redirectURI := "https://route.example.com/"
callbackURI := &url.URL{
Scheme: "https",
Host: "route.example.com",
Path: "/.pomerium/callback",
RawQuery: url.Values{
urlutil.QuerySessionEncrypted: []string{goodEncryptionString},
urlutil.QueryRedirectURI: []string{redirectURI},
urlutil.QueryAdditionalHosts: []string{"foo.example.com,bar.example.com"},
}.Encode(),
}
signedCallbackURI := urlutil.NewSignedURL(sharedKey, callbackURI)
doCallback := func(uri string) *http.Response {
t.Helper()
r := httptest.NewRequest(http.MethodGet, uri, nil)
r.Host = r.URL.Host
w := httptest.NewRecorder()
err = flow.Callback(w, r)
require.NoError(t, err)
return w.Result()
}
// Callback() should serve redirects to the additional hosts before the final redirect URI.
res := doCallback(signedCallbackURI.String())
location, _ := url.Parse(res.Header.Get("Location"))
assert.Equal(t, "foo.example.com", location.Host)
assert.Equal(t, "/.pomerium/callback/", location.Path)
res = doCallback(location.String())
location, _ = url.Parse(res.Header.Get("Location"))
assert.Equal(t, "bar.example.com", location.Host)
assert.Equal(t, "/.pomerium/callback/", location.Path)
res = doCallback(location.String())
location, _ = url.Parse(res.Header.Get("Location"))
assert.Equal(t, "route.example.com", location.Host)
assert.Equal(t, "/", location.Path)
}
func TestStatefulRevokeSession(t *testing.T) {
opts := config.NewDefaultOptions()
flow, err := NewStateful(context.Background(), trace.NewNoopTracerProvider(), &config.Config{Options: opts}, nil)

View file

@ -9,6 +9,9 @@ import (
"net/url"
"github.com/go-jose/go-jose/v3"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
oteltrace "go.opentelemetry.io/otel/trace"
"golang.org/x/oauth2"
googlegrpc "google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
@ -21,7 +24,6 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -31,9 +33,7 @@ import (
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/hpke"
"github.com/pomerium/pomerium/pkg/identity"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Stateless implements the stateless authentication flow. In this flow, the
@ -355,7 +355,11 @@ func getUserClaim(profile *identitypb.Profile, field string) *string {
// AuthenticateSignInURL returns a URL to redirect the user to the authenticate
// domain.
func (s *Stateless) AuthenticateSignInURL(
ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string,
ctx context.Context,
queryParams url.Values,
redirectURL *url.URL,
idpID string,
_ []string,
) (string, error) {
authenticateHPKEPublicKey, err := s.authenticateKeyFetcher.FetchPublicKey(ctx)
if err != nil {

View file

@ -219,10 +219,10 @@ func (mgr *Manager) renewConfigCerts(ctx context.Context) error {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
if len(renew) > 0 {
c = c.Strs("renew_domains", renew)
c = c.Strs("renew-domains", renew)
}
if len(ocsp) > 0 {
c = c.Strs("ocsp_refresh", ocsp)
c = c.Strs("ocsp-refresh", ocsp)
}
return c
})

View file

@ -551,7 +551,7 @@ func Test_configureTrustedRoots(t *testing.T) {
require.NoError(t, err)
ok := roots.AppendCertsFromPEM(ca.certPEM)
require.Equal(t, true, ok)
f, err := os.CreateTemp("", "pomerium-test-ca")
f, err := os.CreateTemp(t.TempDir(), "pomerium-test-ca")
require.NoError(t, err)
n, err := f.Write(ca.certPEM)
require.NoError(t, err)

View file

@ -9,13 +9,14 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/envutil"
"github.com/pomerium/pomerium/internal/testenv/scenarios"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/upstreams"
"github.com/stretchr/testify/assert"
)
var (

Some files were not shown because too many files have changed in this diff Show more