diff --git a/.github/Dockerfile-cloudrun b/.github/Dockerfile-cloudrun index c7ecf5017..d2f6c76d1 100644 --- a/.github/Dockerfile-cloudrun +++ b/.github/Dockerfile-cloudrun @@ -1,5 +1,5 @@ -FROM busybox:latest@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 as build +FROM busybox:latest@sha256:82742949a3709938cbeb9cec79f5eaf3e48b255389f2dcedf2de29ef96fd841c as build RUN touch /config.yaml FROM gcr.io/distroless/base:latest@sha256:1aae189e3baecbb4044c648d356ddb75025b2ba8d14cdc9c2a19ba784c90bfb9 diff --git a/.github/Dockerfile-release b/.github/Dockerfile-release index 2372631be..499563ef1 100644 --- a/.github/Dockerfile-release +++ b/.github/Dockerfile-release @@ -1,4 +1,4 @@ -FROM busybox:latest@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 as build +FROM busybox:latest@sha256:82742949a3709938cbeb9cec79f5eaf3e48b255389f2dcedf2de29ef96fd841c as build RUN touch /config.yaml FROM gcr.io/distroless/base-debian12:latest@sha256:1aae189e3baecbb4044c648d356ddb75025b2ba8d14cdc9c2a19ba784c90bfb9 diff --git a/.github/Dockerfile-release-debug b/.github/Dockerfile-release-debug index 7924ecd87..ed137fc5e 100644 --- a/.github/Dockerfile-release-debug +++ b/.github/Dockerfile-release-debug @@ -1,4 +1,4 @@ -FROM busybox:latest@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 as build +FROM busybox:latest@sha256:82742949a3709938cbeb9cec79f5eaf3e48b255389f2dcedf2de29ef96fd841c as build RUN touch /config.yaml FROM gcr.io/distroless/base-debian12:debug@sha256:af772ed0ce52d8994acedc3ec84a9d22e9366dda8767f17d1bb2213b06beaff5 diff --git a/.github/Dockerfile-release-debug-nonroot b/.github/Dockerfile-release-debug-nonroot index 79b7b08d3..a459cc4b8 100644 --- a/.github/Dockerfile-release-debug-nonroot +++ b/.github/Dockerfile-release-debug-nonroot @@ -1,4 +1,4 @@ -FROM busybox:latest@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 as build +FROM busybox:latest@sha256:82742949a3709938cbeb9cec79f5eaf3e48b255389f2dcedf2de29ef96fd841c as build RUN touch /config.yaml FROM gcr.io/distroless/base-debian12:debug-nonroot@sha256:8d946e4103571ec0e2f471eac7c4859a7f169686d222f5c8b2d9d391d6d1e50c diff --git a/.github/Dockerfile-release-nonroot b/.github/Dockerfile-release-nonroot index 9c99fe2b9..750f4071e 100644 --- a/.github/Dockerfile-release-nonroot +++ b/.github/Dockerfile-release-nonroot @@ -1,4 +1,4 @@ -FROM busybox:latest@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 as build +FROM busybox:latest@sha256:82742949a3709938cbeb9cec79f5eaf3e48b255389f2dcedf2de29ef96fd841c as build RUN touch /config.yaml FROM gcr.io/distroless/base-debian12:nonroot@sha256:a9899ccd9868bbd8913c67f6807410abecf766bc9e3c718eb6248f7b3dfb9819 diff --git a/.github/workflows/docker-main.yaml b/.github/workflows/docker-main.yaml index 6b32d6ab6..92ff4e66c 100644 --- a/.github/workflows/docker-main.yaml +++ b/.github/workflows/docker-main.yaml @@ -45,7 +45,7 @@ jobs: echo "sha-tag=${SHA_TAG}" >> $GITHUB_OUTPUT - name: Docker Publish - Main - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 with: context: . file: ./Dockerfile @@ -58,7 +58,7 @@ jobs: org.opencontainers.image.revision=${{ github.sha }} - name: Docker Publish - Debug - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 with: context: . file: ./Dockerfile.debug @@ -81,7 +81,7 @@ jobs: token: ${{ secrets.APPARITOR_GITHUB_TOKEN }} - name: Bump psql environment - uses: mikefarah/yq@f15500b20a1c991c8729870ba60a4dc3524b6a94 + uses: mikefarah/yq@bbdd97482f2d439126582a59689eb1c855944955 with: cmd: yq eval '.pomerium.image.tag = "${{ needs.publish.outputs.sha-tag }}"' -i diff --git a/.github/workflows/docker-version-branches.yaml b/.github/workflows/docker-version-branches.yaml index 37c495faf..56b161f24 100644 --- a/.github/workflows/docker-version-branches.yaml +++ b/.github/workflows/docker-version-branches.yaml @@ -38,7 +38,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Docker Publish - Version Branches - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 with: context: . file: ./Dockerfile diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 616861995..3208f7a6e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -45,13 +45,13 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: gcloud authenticate - uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa + uses: google-github-actions/auth@62cf5bd3e4211a0a0b51f2c6d6a37129d828611d with: project_id: ${{ secrets.GCP_PRODUCTION_PROJECT_ID }} credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT }} - name: gcloud sdk - uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 + uses: google-github-actions/setup-gcloud@f0990588f1e5b5af6827153b93673613abdc6ec7 - name: Gcloud login run: gcloud auth configure-docker @@ -127,7 +127,7 @@ jobs: token: ${{ secrets.APPARITOR_GITHUB_TOKEN }} - name: Bump test environment - uses: mikefarah/yq@f15500b20a1c991c8729870ba60a4dc3524b6a94 + uses: mikefarah/yq@bbdd97482f2d439126582a59689eb1c855944955 with: cmd: yq eval '.pomerium.image.tag = "${{ needs.goreleaser.outputs.tag }}"' -i projects/pomerium-demo/pomerium-demo/values.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e0266ec80..5da5a33ee 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -76,7 +76,7 @@ jobs: make build - name: save binary - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 with: path: bin/pomerium* name: pomerium ${{ github.run_id }} ${{ matrix.platform }} @@ -108,7 +108,7 @@ jobs: uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db - name: Docker Build - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 with: context: . file: ./Dockerfile @@ -129,7 +129,7 @@ jobs: go-version: 1.23.x cache: false - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: "3.x" diff --git a/Dockerfile b/Dockerfile index fcaaa4c86..46e8ce2c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts-bookworm@sha256:1ae9ba874435551280e95c8a8e74adf8a48d72b564bf9dfe4718231f2144c88f as ui +FROM node:lts-bookworm@sha256:a4d1de4c7339eabcf78a90137dfd551b798829e3ef3e399e0036ac454afa1291 as ui WORKDIR /build COPY .git ./.git @@ -13,7 +13,7 @@ RUN make yarn COPY ./ui/ ./ui/ RUN make build-ui -FROM golang:1.23-bookworm@sha256:4bda3420962ddfc78467f81eb9c2880e29e53604c6c05eae597210fc0fe8b5bf as build +FROM golang:1.23-bookworm@sha256:31dc846dd1bcca84d2fa231bcd16c09ff271bcc1a5ae2c48ff10f13b039688f3 as build WORKDIR /go/src/github.com/pomerium/pomerium RUN apt-get update \ diff --git a/Dockerfile.debug b/Dockerfile.debug index 681a234ab..5ddf955d8 100644 --- a/Dockerfile.debug +++ b/Dockerfile.debug @@ -1,4 +1,4 @@ -FROM node:lts-bookworm@sha256:1ae9ba874435551280e95c8a8e74adf8a48d72b564bf9dfe4718231f2144c88f as ui +FROM node:lts-bookworm@sha256:a4d1de4c7339eabcf78a90137dfd551b798829e3ef3e399e0036ac454afa1291 as ui WORKDIR /build COPY .git ./.git @@ -13,7 +13,7 @@ RUN make yarn COPY ./ui/ ./ui/ RUN make build-ui -FROM golang:1.23-bookworm@sha256:4bda3420962ddfc78467f81eb9c2880e29e53604c6c05eae597210fc0fe8b5bf as build +FROM golang:1.23-bookworm@sha256:31dc846dd1bcca84d2fa231bcd16c09ff271bcc1a5ae2c48ff10f13b039688f3 as build WORKDIR /go/src/github.com/pomerium/pomerium RUN apt-get update \ diff --git a/README.md b/README.md index 28d005a46..d9cd90e6d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Pomerium is: - **Safer** because [every single action is verified](https://www.pomerium.com/continuous-verification-auditing) before allowed to execute. - **Tailored** to your organization’s needs by integrating all data for [context-aware access](https://www.pomerium.com/context-aware-access). -It’s not a VPN alternative – it’s the trusted, foolproof way to protect your business. [Give Pomerium a try today](https://console.pomerium.app/create-account?utm_source=github&utm_medium=readme&utm_campaign=github)! +It’s not a VPN alternative – it’s the trusted, foolproof way to protect your business. Want a hosted control plane and management GUI? [Give Pomerium Zero a try today](https://console.pomerium.app/create-account?utm_source=github&utm_medium=readme&utm_campaign=github)! ## Docs diff --git a/config/runtime_flags.go b/config/runtime_flags.go index c69ffa616..fe01550e6 100644 --- a/config/runtime_flags.go +++ b/config/runtime_flags.go @@ -3,20 +3,22 @@ package config import "maps" var ( - // RuntimeFlagGRPCDatabrokerKeepalive enables gRPC keepalive to the databroker service - RuntimeFlagGRPCDatabrokerKeepalive = runtimeFlag("grpc_databroker_keepalive", false) - - // RuntimeFlagMatchAnyIncomingPort enables ignoring the incoming port when matching routes - RuntimeFlagMatchAnyIncomingPort = runtimeFlag("match_any_incoming_port", true) - - // RuntimeFlagLegacyIdentityManager enables the legacy identity manager - RuntimeFlagLegacyIdentityManager = runtimeFlag("legacy_identity_manager", false) - // RuntimeFlagConfigHotReload enables the hot-reloading mechanism for the config file // and any other files referenced within it RuntimeFlagConfigHotReload = runtimeFlag("config_hot_reload", true) - RuntimeFlagEnvoyResourceManagerEnabled = runtimeFlag("envoy_resource_manager_enabled", true) + // RuntimeFlagEnvoyResourceManager enables Envoy overload settings based on + // process cgroup limits (Linux only). + RuntimeFlagEnvoyResourceManager = runtimeFlag("envoy_resource_manager", true) + + // 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) // RuntimeFlagPomeriumJWTEndpoint enables the /.pomerium/jwt endpoint, for retrieving // signed user info claims from an upstream single-page web application. This endpoint diff --git a/go.mod b/go.mod index fc903973a..549e0c732 100644 --- a/go.mod +++ b/go.mod @@ -180,7 +180,7 @@ require ( github.com/onsi/gomega v1.30.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.12 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/philhofer/fwd v1.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 10aee673f..e16b3ae0a 100644 --- a/go.sum +++ b/go.sum @@ -489,8 +489,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 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/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= diff --git a/internal/zero/api/api.go b/internal/zero/api/api.go index 9f62e0936..b83d9b1a7 100644 --- a/internal/zero/api/api.go +++ b/internal/zero/api/api.go @@ -63,7 +63,7 @@ func NewAPI(ctx context.Context, opts ...Option) (*API, error) { tokenCache := token_api.NewCache(fetcher, cfg.apiToken) - clusterClient, err := cluster_api.NewAuthorizedClient(cfg.clusterAPIEndpoint, tokenCache.GetToken, cfg.httpClient) + clusterClient, err := cluster_api.NewAuthorizedClient(cfg.clusterAPIEndpoint, tokenCache, cfg.httpClient) if err != nil { return nil, fmt.Errorf("error creating cluster client: %w", err) } @@ -104,14 +104,14 @@ func (api *API) Watch(ctx context.Context, opts ...WatchOption) error { // GetClusterBootstrapConfig fetches the bootstrap configuration from the cluster API func (api *API) GetClusterBootstrapConfig(ctx context.Context) (*cluster_api.BootstrapConfig, error) { - return apierror.CheckResponse[cluster_api.BootstrapConfig]( + return apierror.CheckResponse( api.cluster.GetClusterBootstrapConfigWithResponse(ctx), ) } // GetClusterResourceBundles fetches the resource bundles from the cluster API func (api *API) GetClusterResourceBundles(ctx context.Context) (*cluster_api.GetBundlesResponse, error) { - return apierror.CheckResponse[cluster_api.GetBundlesResponse]( + return apierror.CheckResponse( api.cluster.GetClusterResourceBundlesWithResponse(ctx), ) } diff --git a/internal/zero/api/download.go b/internal/zero/api/download.go index f536badbf..0a7463f5c 100644 --- a/internal/zero/api/download.go +++ b/internal/zero/api/download.go @@ -56,6 +56,10 @@ func (api *API) DownloadClusterResourceBundle( return newContentNotModifiedDownloadResult(resp.Header.Get("Last-Modified") != current.LastModified), nil } + if resp.StatusCode == http.StatusUnauthorized { + api.downloadURLCache.Delete(id) + } + if resp.StatusCode != http.StatusOK { return nil, httpDownloadError(ctx, resp) } @@ -107,6 +111,10 @@ func (api *API) HeadClusterResourceBundle( Str("status", resp.Status). Msg("bundle metadata request") + if resp.StatusCode == http.StatusUnauthorized { + api.downloadURLCache.Delete(id) + } + if resp.StatusCode != http.StatusOK { return nil, httpDownloadError(ctx, resp) } @@ -180,7 +188,7 @@ func (api *API) getDownloadParams(ctx context.Context, id string) (*cluster_api. func (api *API) updateBundleDownloadParams(ctx context.Context, id string) (*cluster_api.DownloadCacheEntry, error) { now := time.Now() - resp, err := apierror.CheckResponse[cluster_api.DownloadBundleResponse]( + resp, err := apierror.CheckResponse( api.cluster.DownloadClusterResourceBundleWithResponse(ctx, id), ) if err != nil { @@ -197,11 +205,13 @@ func (api *API) updateBundleDownloadParams(ctx context.Context, id string) (*clu return nil, fmt.Errorf("parse url: %w", err) } + expires := now.Add(time.Duration(expiresSeconds) * time.Second) param := cluster_api.DownloadCacheEntry{ URL: *u, - ExpiresAt: now.Add(time.Duration(expiresSeconds) * time.Second), + ExpiresAt: expires, CaptureHeaders: resp.CaptureMetadataHeaders, } + log.Ctx(ctx).Debug().Time("expires", expires).Msg("bundle download URL updated") api.downloadURLCache.Set(id, param) return ¶m, nil } @@ -323,7 +333,7 @@ func isXML(ct string) bool { } func extractMetadata(header http.Header, keys []string) map[string]string { - log.Info().Interface("header", header).Msg("extract metadata") + log.Debug().Interface("header", header).Msg("extract metadata") m := make(map[string]string) for _, k := range keys { v := header.Get(k) diff --git a/internal/zero/apierror/response.go b/internal/zero/apierror/response.go index ef991c917..5d2afdb25 100644 --- a/internal/zero/apierror/response.go +++ b/internal/zero/apierror/response.go @@ -47,6 +47,19 @@ func responseError[T any](resp APIResponse[T]) error { if ok { return fmt.Errorf("internal server error: %v", reason) } + + if f, ok := resp.(interface{ GetForbiddenError() (string, bool) }); ok { + if reason, ok := f.GetForbiddenError(); ok { + return fmt.Errorf("forbidden: %v", reason) + } + } + + if f, ok := resp.(interface{ GetNotFoundError() (string, bool) }); ok { + if reason, ok := f.GetNotFoundError(); ok { + return fmt.Errorf("not found: %v", reason) + } + } + //nolint:bodyclose httpResp := resp.GetHTTPResponse() if httpResp == nil { diff --git a/internal/zero/token/cache.go b/internal/zero/token/cache.go index abd0a1a06..bcd119ea5 100644 --- a/internal/zero/token/cache.go +++ b/internal/zero/token/cache.go @@ -3,7 +3,6 @@ package token import ( "context" - "fmt" "sync/atomic" "time" ) @@ -21,7 +20,7 @@ type Cache struct { fetcher Fetcher lock chan struct{} - token atomic.Value + token atomic.Pointer[Token] } // Fetcher is a function that fetches a new token @@ -42,11 +41,13 @@ func (t *Token) ExpiresAfter(tm time.Time) bool { // NewCache creates a new token cache func NewCache(fetcher Fetcher, refreshToken string) *Cache { - return &Cache{ + c := &Cache{ lock: make(chan struct{}, 1), fetcher: fetcher, refreshToken: refreshToken, } + c.token.Store(nil) + return c } func (c *Cache) timeNow() time.Time { @@ -56,19 +57,33 @@ func (c *Cache) timeNow() time.Time { return time.Now() } +func (c *Cache) Reset() { + c.token.Store(nil) +} + // GetToken returns the current token if its at least `minTTL` from expiration, or fetches a new one. func (c *Cache) GetToken(ctx context.Context, minTTL time.Duration) (string, error) { minExpiration := c.timeNow().Add(minTTL) - token, ok := c.token.Load().(*Token) - if ok && token.ExpiresAfter(minExpiration) { + token := c.token.Load() + if token.ExpiresAfter(minExpiration) { return token.Bearer, nil } - return c.forceRefreshToken(ctx, minExpiration) + return c.ForceRefreshToken(ctx) } -func (c *Cache) forceRefreshToken(ctx context.Context, minExpiration time.Time) (string, error) { +func (c *Cache) ForceRefreshToken(ctx context.Context) (string, error) { + var current string + token := c.token.Load() + if token != nil { + current = token.Bearer + } + + return c.forceRefreshToken(ctx, current) +} + +func (c *Cache) forceRefreshToken(ctx context.Context, current string) (string, error) { select { case c.lock <- struct{}{}: case <-ctx.Done(): @@ -81,8 +96,8 @@ func (c *Cache) forceRefreshToken(ctx context.Context, minExpiration time.Time) ctx, cancel := context.WithTimeout(ctx, maxLockWait) defer cancel() - token, ok := c.token.Load().(*Token) - if ok && token.ExpiresAfter(minExpiration) { + token := c.token.Load() + if token != nil && token.Bearer != current { return token.Bearer, nil } @@ -92,9 +107,5 @@ func (c *Cache) forceRefreshToken(ctx context.Context, minExpiration time.Time) } c.token.Store(token) - if token.Expires.Before(minExpiration) { - return "", fmt.Errorf("new token cannot satisfy TTL: %v", minExpiration.Sub(token.Expires)) - } - return token.Bearer, nil } diff --git a/internal/zero/token/cache_test.go b/internal/zero/token/cache_test.go index f8de4b363..6a55bf9e5 100644 --- a/internal/zero/token/cache_test.go +++ b/internal/zero/token/cache_test.go @@ -2,6 +2,7 @@ package token_test import ( "context" + "fmt" "testing" "time" @@ -51,15 +52,28 @@ func TestCache(t *testing.T) { assert.Equal(t, "bearer-3", bearer) }) - t.Run("token cannot fit minTTL", func(t *testing.T) { + t.Run("reset", func(t *testing.T) { t.Parallel() + var calls int fetcher := func(_ context.Context, _ string) (*token.Token, error) { - return &token.Token{"ok-bearer", time.Now().Add(time.Minute)}, nil + calls++ + return &token.Token{fmt.Sprintf("bearer-%d", calls), time.Now().Add(time.Hour)}, nil } + ctx := context.Background() c := token.NewCache(fetcher, "test-refresh-token") - _, err := c.GetToken(context.Background(), time.Minute*2) - assert.Error(t, err) + got, err := c.GetToken(ctx, time.Minute*2) + assert.NoError(t, err) + assert.Equal(t, "bearer-1", got) + + got, err = c.GetToken(ctx, time.Minute*2) + assert.NoError(t, err) + assert.Equal(t, "bearer-1", got) + + c.Reset() + got, err = c.GetToken(ctx, time.Minute*2) + assert.NoError(t, err) + assert.Equal(t, "bearer-2", got) }) } diff --git a/k8s/zero/deployment/base.yaml b/k8s/zero/deployment/base.yaml index ece3b8337..5d5254568 100644 --- a/k8s/zero/deployment/base.yaml +++ b/k8s/zero/deployment/base.yaml @@ -1,15 +1,15 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: - serviceName: "pomerium-proxy" replicas: 1 selector: matchLabels: app.kubernetes.io/name: pomerium-zero template: spec: + serviceAccountName: pomerium-zero containers: - name: pomerium terminationGracePeriodSeconds: 10 diff --git a/k8s/zero/deployment/env.yaml b/k8s/zero/deployment/env.yaml index c0ad02eb1..6cb28d7f9 100644 --- a/k8s/zero/deployment/env.yaml +++ b/k8s/zero/deployment/env.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: @@ -19,6 +19,10 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.namespace + - name: BOOTSTRAP_CONFIG_FILE + value: "/var/run/secrets/pomerium/bootstrap.dat" + - name: BOOTSTRAP_CONFIG_WRITEBACK_URI + value: "secret://$(POMERIUM_NAMESPACE)/pomerium/bootstrap" - name: POD_IP valueFrom: fieldRef: diff --git a/k8s/zero/deployment/image.yaml b/k8s/zero/deployment/image.yaml index ea4fb07cc..671d47e6d 100644 --- a/k8s/zero/deployment/image.yaml +++ b/k8s/zero/deployment/image.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: diff --git a/k8s/zero/deployment/no-root.yaml b/k8s/zero/deployment/no-root.yaml index f00cb3746..b708193f8 100644 --- a/k8s/zero/deployment/no-root.yaml +++ b/k8s/zero/deployment/no-root.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: diff --git a/k8s/zero/deployment/ports.yaml b/k8s/zero/deployment/ports.yaml index 08181d539..547e777b0 100644 --- a/k8s/zero/deployment/ports.yaml +++ b/k8s/zero/deployment/ports.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: diff --git a/k8s/zero/deployment/readonly-root-fs.yaml b/k8s/zero/deployment/readonly-root-fs.yaml index 7c5c98641..7159c9eab 100644 --- a/k8s/zero/deployment/readonly-root-fs.yaml +++ b/k8s/zero/deployment/readonly-root-fs.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: diff --git a/k8s/zero/deployment/resources.yaml b/k8s/zero/deployment/resources.yaml index bad226505..21a6ee65d 100644 --- a/k8s/zero/deployment/resources.yaml +++ b/k8s/zero/deployment/resources.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: diff --git a/k8s/zero/deployment/volumes.yaml b/k8s/zero/deployment/volumes.yaml index 871df36bf..15475674c 100644 --- a/k8s/zero/deployment/volumes.yaml +++ b/k8s/zero/deployment/volumes.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: StatefulSet +kind: Deployment metadata: name: pomerium spec: @@ -13,22 +13,22 @@ spec: - name: TMPDIR value: "/tmp/pomerium" - name: XDG_CACHE_HOME - value: "/var/cache" + value: "/tmp/pomerium/cache" - name: XDG_DATA_HOME - value: "/var/cache" + value: "/tmp/pomerium/cache" volumeMounts: - mountPath: "/tmp/pomerium" name: tmp - - mountPath: "/var/cache" - name: pomerium-cache + - mountPath: "/var/run/secrets/pomerium" + name: bootstrap + readOnly: true volumes: - name: tmp emptyDir: {} - volumeClaimTemplates: - - metadata: - name: pomerium-cache - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 100Mi + - name: bootstrap + secret: + optional: true + secretName: pomerium + items: + - key: bootstrap + path: bootstrap.dat diff --git a/k8s/zero/kustomization.yaml b/k8s/zero/kustomization.yaml index bed5cf7ed..60afd7e76 100644 --- a/k8s/zero/kustomization.yaml +++ b/k8s/zero/kustomization.yaml @@ -3,5 +3,6 @@ commonLabels: app.kubernetes.io/name: pomerium-zero resources: - namespace.yaml + - ./rbac - ./deployment - ./service diff --git a/k8s/zero/rbac/kustomization.yaml b/k8s/zero/rbac/kustomization.yaml new file mode 100644 index 000000000..6da23f403 --- /dev/null +++ b/k8s/zero/rbac/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- role.yaml +- role_binding.yaml +- service_account.yaml diff --git a/k8s/zero/rbac/role.yaml b/k8s/zero/rbac/role.yaml new file mode 100644 index 000000000..25a7bd64c --- /dev/null +++ b/k8s/zero/rbac/role.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pomerium-zero +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - patch + resourceNames: + - pomerium diff --git a/k8s/zero/rbac/role_binding.yaml b/k8s/zero/rbac/role_binding.yaml new file mode 100644 index 000000000..6f6bc3af5 --- /dev/null +++ b/k8s/zero/rbac/role_binding.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: pomerium-zero +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: pomerium-zero +subjects: + - kind: ServiceAccount + name: pomerium-zero diff --git a/k8s/zero/rbac/service_account.yaml b/k8s/zero/rbac/service_account.yaml new file mode 100644 index 000000000..6ad260336 --- /dev/null +++ b/k8s/zero/rbac/service_account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pomerium-zero diff --git a/pkg/envoy/resource_monitor_linux.go b/pkg/envoy/resource_monitor_linux.go index 57f22836f..1df6f5f8c 100644 --- a/pkg/envoy/resource_monitor_linux.go +++ b/pkg/envoy/resource_monitor_linux.go @@ -204,10 +204,10 @@ type sharedResourceMonitor struct { func (s *sharedResourceMonitor) onConfigChange(_ context.Context, cfg *config.Config) { if cfg == nil || cfg.Options == nil { - s.enabled.Store(config.DefaultRuntimeFlags()[config.RuntimeFlagEnvoyResourceManagerEnabled]) + s.enabled.Store(config.DefaultRuntimeFlags()[config.RuntimeFlagEnvoyResourceManager]) return } - s.enabled.Store(cfg.Options.IsRuntimeFlagSet(config.RuntimeFlagEnvoyResourceManagerEnabled)) + s.enabled.Store(cfg.Options.IsRuntimeFlagSet(config.RuntimeFlagEnvoyResourceManager)) } func (s *sharedResourceMonitor) metricFilename(group, name string) string { diff --git a/pkg/envoy/resource_monitor_test.go b/pkg/envoy/resource_monitor_test.go index 907dd2755..8210f84e3 100644 --- a/pkg/envoy/resource_monitor_test.go +++ b/pkg/envoy/resource_monitor_test.go @@ -665,7 +665,7 @@ func TestSharedResourceMonitor(t *testing.T) { configSrc.SetConfig(ctx, &config.Config{ Options: &config.Options{ RuntimeFlags: config.RuntimeFlags{ - config.RuntimeFlagEnvoyResourceManagerEnabled: false, + config.RuntimeFlagEnvoyResourceManager: false, }, }, }) @@ -677,7 +677,7 @@ func TestSharedResourceMonitor(t *testing.T) { configSrc.SetConfig(ctx, &config.Config{ Options: &config.Options{ RuntimeFlags: config.RuntimeFlags{ - config.RuntimeFlagEnvoyResourceManagerEnabled: true, + config.RuntimeFlagEnvoyResourceManager: true, }, }, }) diff --git a/pkg/zero/cluster/client-with-responses.tmpl b/pkg/zero/cluster/client-with-responses.tmpl new file mode 100644 index 000000000..e837c0c67 --- /dev/null +++ b/pkg/zero/cluster/client-with-responses.tmpl @@ -0,0 +1,207 @@ +{{/* +This file contains the original client-with-responses.tmpl, plus some additional +code to generate custom response functions. +*/}} + +{{/* Begin upstream code /*}} + +{{/* +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/}} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +{{$clientTypeName := opts.OutputOptions.ClientTypeName -}} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *{{ $clientTypeName }}) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) +{{range .Bodies}} + {{if .IsSupportedByClient -}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) + {{end -}} +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + +{{range .}}{{$opid := .OperationId}}{{$op := .}} +type {{genResponseTypeName $opid | ucFirst}} struct { + Body []byte + HTTPResponse *http.Response + {{- range getResponseTypeDefinitions .}} + {{.TypeName}} *{{.Schema.TypeDecl}} + {{- end}} +} + +// Status returns HTTPResponse.Status +func (r {{genResponseTypeName $opid | ucFirst}}) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r {{genResponseTypeName $opid | ucFirst}}) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} +{{end}} + + +{{range .}} +{{$opid := .OperationId -}} +{{/* Generate client methods (with responses)*/}} + +// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with arbitrary body{{end}} returning *{{genResponseTypeName $opid}} +func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error){ + rsp, err := c.{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}, reqEditors...) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} + +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{range .Bodies}} +{{if .IsSupportedByClient -}} +func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) { + rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} +{{end}} +{{end}} + +{{end}}{{/* operations */}} + +{{/* Generate parse functions for responses*/}} +{{range .}}{{$opid := .OperationId}} + +// Parse{{genResponseTypeName $opid | ucFirst}} parses an HTTP response from a {{$opid}}WithResponse call +func Parse{{genResponseTypeName $opid | ucFirst}}(rsp *http.Response) (*{{genResponseTypeName $opid}}, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := {{genResponsePayload $opid}} + + {{genResponseUnmarshal .}} + + return response, nil +} +{{end}}{{/* range . $opid := .OperationId */}} + +{{/* End upstream code /*}} + + +{{/* Custom code below */}} + +{{range .}}{{$opid := .OperationId}}{{$op := .}} +// GetHTTPResponse implements apierror.APIResponse +func (r *{{genResponseTypeName $opid | ucFirst}}) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + {{- $has200Resp := false -}} + {{- range getResponseTypeDefinitions .}} + {{- if (eq .TypeName "JSON200")}} + {{- $has200Resp = true}} +// GetValue implements apierror.APIResponse +func (r *{{genResponseTypeName $opid | ucFirst}}) GetValue() *{{.Schema.TypeDecl}} { + return r.JSON200 +} + {{- else if (eq .TypeName "JSON400")}} +// GetBadRequestError implements apierror.APIResponse +func (r *{{genResponseTypeName $opid | ucFirst}}) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + {{- else if (eq .TypeName "JSON404")}} + +func (r *{{genResponseTypeName $opid | ucFirst}}) GetNotFoundError() (string, bool) { + if r.JSON404 == nil { + return "", false + } + return r.JSON404.Error, true +} + {{- else if (eq .TypeName "JSON403")}} + +func (r *{{genResponseTypeName $opid | ucFirst}}) GetForbiddenError() (string, bool) { + if r.JSON403 == nil { + return "", false + } + return r.JSON403.Error, true +} + {{- else if (eq .TypeName "JSON500")}} +// GetInternalServerError implements apierror.APIResponse +func (r *{{genResponseTypeName $opid | ucFirst}}) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} + + {{- end}} + {{- end}} + {{- if (not $has200Resp)}} +// GetValue implements apierror.APIResponse +func (r *{{genResponseTypeName $opid | ucFirst}}) GetValue() *EmptyResponse { + if r.StatusCode()/100 != 2 { + return nil + } + return &EmptyResponse{} +} + {{- end}} +{{end}} diff --git a/pkg/zero/cluster/client.gen.go b/pkg/zero/cluster/client.gen.go index 0f179fd97..21e183063 100644 --- a/pkg/zero/cluster/client.gen.go +++ b/pkg/zero/cluster/client.gen.go @@ -811,3 +811,143 @@ func ParseExchangeClusterIdentityTokenResp(rsp *http.Response) (*ExchangeCluster return response, nil } + +// GetHTTPResponse implements apierror.APIResponse +func (r *GetClusterBootstrapConfigResp) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + +// GetValue implements apierror.APIResponse +func (r *GetClusterBootstrapConfigResp) GetValue() *GetBootstrapConfigResponse { + return r.JSON200 +} + +// GetBadRequestError implements apierror.APIResponse +func (r *GetClusterBootstrapConfigResp) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + +// GetInternalServerError implements apierror.APIResponse +func (r *GetClusterBootstrapConfigResp) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} + +// GetHTTPResponse implements apierror.APIResponse +func (r *GetClusterResourceBundlesResp) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + +// GetValue implements apierror.APIResponse +func (r *GetClusterResourceBundlesResp) GetValue() *GetBundlesResponse { + return r.JSON200 +} + +// GetBadRequestError implements apierror.APIResponse +func (r *GetClusterResourceBundlesResp) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + +// GetInternalServerError implements apierror.APIResponse +func (r *GetClusterResourceBundlesResp) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} + +// GetHTTPResponse implements apierror.APIResponse +func (r *DownloadClusterResourceBundleResp) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + +// GetValue implements apierror.APIResponse +func (r *DownloadClusterResourceBundleResp) GetValue() *DownloadBundleResponse { + return r.JSON200 +} + +// GetBadRequestError implements apierror.APIResponse +func (r *DownloadClusterResourceBundleResp) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + +func (r *DownloadClusterResourceBundleResp) GetNotFoundError() (string, bool) { + if r.JSON404 == nil { + return "", false + } + return r.JSON404.Error, true +} + +// GetInternalServerError implements apierror.APIResponse +func (r *DownloadClusterResourceBundleResp) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} + +// GetHTTPResponse implements apierror.APIResponse +func (r *ReportClusterResourceBundleStatusResp) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + +// GetBadRequestError implements apierror.APIResponse +func (r *ReportClusterResourceBundleStatusResp) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + +// GetInternalServerError implements apierror.APIResponse +func (r *ReportClusterResourceBundleStatusResp) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} + +// GetValue implements apierror.APIResponse +func (r *ReportClusterResourceBundleStatusResp) GetValue() *EmptyResponse { + if r.StatusCode()/100 != 2 { + return nil + } + return &EmptyResponse{} +} + +// GetHTTPResponse implements apierror.APIResponse +func (r *ExchangeClusterIdentityTokenResp) GetHTTPResponse() *http.Response { + return r.HTTPResponse +} + +// GetValue implements apierror.APIResponse +func (r *ExchangeClusterIdentityTokenResp) GetValue() *ExchangeTokenResponse { + return r.JSON200 +} + +// GetBadRequestError implements apierror.APIResponse +func (r *ExchangeClusterIdentityTokenResp) GetBadRequestError() (string, bool) { + if r.JSON400 == nil { + return "", false + } + return r.JSON400.Error, true +} + +// GetInternalServerError implements apierror.APIResponse +func (r *ExchangeClusterIdentityTokenResp) GetInternalServerError() (string, bool) { + if r.JSON500 == nil { + return "", false + } + return r.JSON500.Error, true +} diff --git a/pkg/zero/cluster/client.go b/pkg/zero/cluster/client.go index 932e88f4a..066c37f81 100644 --- a/pkg/zero/cluster/client.go +++ b/pkg/zero/cluster/client.go @@ -17,18 +17,23 @@ const ( var userAgent = version.UserAgent() type client struct { - tokenProvider TokenProviderFn + tokenProvider TokenCache httpClient *http.Client minTokenTTL time.Duration } -// TokenProviderFn is a function that returns a token that is expected to be valid for at least minTTL -type TokenProviderFn func(ctx context.Context, minTTL time.Duration) (string, error) +// TokenCache interface for fetching and caching tokens +type TokenCache interface { + // GetToken returns a token that is expected to be valid for at least minTTL duration + GetToken(ctx context.Context, minTTL time.Duration) (string, error) + // Reset resets the token cache + Reset() +} // NewAuthorizedClient creates a new HTTP client that will automatically add an authorization header func NewAuthorizedClient( endpoint string, - tokenProvider TokenProviderFn, + tokenProvider TokenCache, httpClient *http.Client, ) (ClientWithResponsesInterface, error) { c := &client{ @@ -43,12 +48,21 @@ func NewAuthorizedClient( func (c *client) Do(req *http.Request) (*http.Response, error) { ctx := req.Context() - token, err := c.tokenProvider(ctx, c.minTokenTTL) + token, err := c.tokenProvider.GetToken(ctx, c.minTokenTTL) if err != nil { return nil, fmt.Errorf("error getting token: %w", err) } req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("User-Agent", userAgent) - return c.httpClient.Do(req) + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusUnauthorized { + c.tokenProvider.Reset() + } + + return resp, nil } diff --git a/pkg/zero/cluster/client.yaml b/pkg/zero/cluster/client.yaml index d1e7c1b65..e09e07800 100644 --- a/pkg/zero/cluster/client.yaml +++ b/pkg/zero/cluster/client.yaml @@ -8,3 +8,5 @@ output-options: # We use Response suffix internally throughout the response objects, # that conflicts with generated client response-type-suffix: Resp + user-templates: + client-with-responses.tmpl: ./client-with-responses.tmpl diff --git a/pkg/zero/cluster/client_errors.go b/pkg/zero/cluster/client_errors.go index fdd2432c1..8867a99f2 100644 --- a/pkg/zero/cluster/client_errors.go +++ b/pkg/zero/cluster/client_errors.go @@ -1,8 +1,6 @@ package cluster import ( - "net/http" - "github.com/pomerium/pomerium/internal/zero/apierror" ) @@ -16,133 +14,3 @@ var ( _ apierror.APIResponse[DownloadBundleResponse] = (*DownloadClusterResourceBundleResp)(nil) _ apierror.APIResponse[EmptyResponse] = (*ReportClusterResourceBundleStatusResp)(nil) ) - -// GetBadRequestError implements apierror.APIResponse -func (r *ExchangeClusterIdentityTokenResp) GetBadRequestError() (string, bool) { - if r.JSON400 == nil { - return "", false - } - return r.JSON400.Error, true -} - -// GetInternalServerError implements apierror.APIResponse -func (r *ExchangeClusterIdentityTokenResp) GetInternalServerError() (string, bool) { - if r.JSON500 == nil { - return "", false - } - return r.JSON500.Error, true -} - -// GetValue implements apierror.APIResponse -func (r *ExchangeClusterIdentityTokenResp) GetValue() *ExchangeTokenResponse { - return r.JSON200 -} - -// GetHTTPResponse implements apierror.APIResponse -func (r *ExchangeClusterIdentityTokenResp) GetHTTPResponse() *http.Response { - return r.HTTPResponse -} - -// GetBadRequestError implements apierror.APIResponse -func (r *GetClusterBootstrapConfigResp) GetBadRequestError() (string, bool) { - if r.JSON400 == nil { - return "", false - } - return r.JSON400.Error, true -} - -// GetInternalServerError implements apierror.APIResponse -func (r *GetClusterBootstrapConfigResp) GetInternalServerError() (string, bool) { - if r.JSON500 == nil { - return "", false - } - return r.JSON500.Error, true -} - -// GetValue implements apierror.APIResponse -func (r *GetClusterBootstrapConfigResp) GetValue() *BootstrapConfig { - return r.JSON200 -} - -// GetHTTPResponse implements apierror.APIResponse -func (r *GetClusterBootstrapConfigResp) GetHTTPResponse() *http.Response { - return r.HTTPResponse -} - -// GetBadRequestError implements apierror.APIResponse -func (r *GetClusterResourceBundlesResp) GetBadRequestError() (string, bool) { - if r.JSON400 == nil { - return "", false - } - return r.JSON400.Error, true -} - -// GetInternalServerError implements apierror.APIResponse -func (r *GetClusterResourceBundlesResp) GetInternalServerError() (string, bool) { - if r.JSON500 == nil { - return "", false - } - return r.JSON500.Error, true -} - -// GetValue implements apierror.APIResponse -func (r *GetClusterResourceBundlesResp) GetValue() *GetBundlesResponse { - return r.JSON200 -} - -// GetHTTPResponse implements apierror.APIResponse -func (r *GetClusterResourceBundlesResp) GetHTTPResponse() *http.Response { - return r.HTTPResponse -} - -// GetBadRequestError implements apierror.APIResponse -func (r *DownloadClusterResourceBundleResp) GetBadRequestError() (string, bool) { - if r.JSON400 == nil { - return "", false - } - return r.JSON400.Error, true -} - -// GetInternalServerError implements apierror.APIResponse -func (r *DownloadClusterResourceBundleResp) GetInternalServerError() (string, bool) { - if r.JSON500 == nil { - return "", false - } - return r.JSON500.Error, true -} - -// GetValue implements apierror.APIResponse -func (r *DownloadClusterResourceBundleResp) GetValue() *DownloadBundleResponse { - return r.JSON200 -} - -// GetHTTPResponse implements apierror.APIResponse -func (r *DownloadClusterResourceBundleResp) GetHTTPResponse() *http.Response { - return r.HTTPResponse -} - -// GetBadRequestError implements apierror.APIResponse -func (r *ReportClusterResourceBundleStatusResp) GetBadRequestError() (string, bool) { - if r.JSON400 == nil { - return "", false - } - return r.JSON400.Error, true -} - -// GetInternalServerError implements apierror.APIResponse -func (r *ReportClusterResourceBundleStatusResp) GetInternalServerError() (string, bool) { - if r.JSON500 == nil { - return "", false - } - return r.JSON500.Error, true -} - -// GetValue implements apierror.APIResponse -func (r *ReportClusterResourceBundleStatusResp) GetValue() *EmptyResponse { - return &EmptyResponse{} -} - -// GetHTTPResponse implements apierror.APIResponse -func (r *ReportClusterResourceBundleStatusResp) GetHTTPResponse() *http.Response { - return r.HTTPResponse -} diff --git a/pkg/zero/cluster/client_test.go b/pkg/zero/cluster/client_test.go index 0de8f2cdb..182ebf9df 100644 --- a/pkg/zero/cluster/client_test.go +++ b/pkg/zero/cluster/client_test.go @@ -46,7 +46,7 @@ func TestAPIClient(t *testing.T) { require.NoError(t, err) tokenCache := token.NewCache(fetcher, "refresh-token") - client, err := api.NewAuthorizedClient(srv.URL, tokenCache.GetToken, http.DefaultClient) + client, err := api.NewAuthorizedClient(srv.URL, tokenCache, http.DefaultClient) require.NoError(t, err) resp, err := client.ExchangeClusterIdentityTokenWithResponse(context.Background(), diff --git a/pkg/zero/cluster/token_fetcher.go b/pkg/zero/cluster/token_fetcher.go index 76f6d87d3..ba1e040de 100644 --- a/pkg/zero/cluster/token_fetcher.go +++ b/pkg/zero/cluster/token_fetcher.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/zero/apierror" "github.com/pomerium/pomerium/internal/zero/token" ) @@ -20,7 +21,7 @@ func NewTokenFetcher(endpoint string, opts ...ClientOption) (token.Fetcher, erro return func(ctx context.Context, refreshToken string) (*token.Token, error) { now := time.Now() - resp, err := apierror.CheckResponse[ExchangeTokenResponse](client.ExchangeClusterIdentityTokenWithResponse(ctx, ExchangeTokenRequest{ + resp, err := apierror.CheckResponse(client.ExchangeClusterIdentityTokenWithResponse(ctx, ExchangeTokenRequest{ RefreshToken: refreshToken, })) if err != nil { @@ -32,9 +33,11 @@ func NewTokenFetcher(endpoint string, opts ...ClientOption) (token.Fetcher, erro return nil, fmt.Errorf("error parsing expires in: %w", err) } + expires := now.Add(time.Duration(expiresSeconds) * time.Second) + log.Ctx(ctx).Debug().Time("expires", expires).Msg("fetched new Bearer token") return &token.Token{ Bearer: resp.IdToken, - Expires: now.Add(time.Duration(expiresSeconds) * time.Second), + Expires: expires, }, nil }, nil } diff --git a/pkg/zero/cluster/urlcache.go b/pkg/zero/cluster/urlcache.go index 09350c931..d7d8a1189 100644 --- a/pkg/zero/cluster/urlcache.go +++ b/pkg/zero/cluster/urlcache.go @@ -29,6 +29,13 @@ func NewURLCache() *URLCache { } } +func (c *URLCache) Delete(key string) { + c.mx.Lock() + defer c.mx.Unlock() + + delete(c.cache, key) +} + // Get gets the cache entry for the given key, if it exists and has not expired. func (c *URLCache) Get(key string, minTTL time.Duration) (*DownloadCacheEntry, bool) { c.mx.RLock()