mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-24 13:38:17 +02:00
Merge branch 'main' into kenjenkins/deprecate-jwt-endpoint
This commit is contained in:
commit
c36043f67f
43 changed files with 553 additions and 222 deletions
2
.github/Dockerfile-cloudrun
vendored
2
.github/Dockerfile-cloudrun
vendored
|
@ -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
|
||||
|
|
2
.github/Dockerfile-release
vendored
2
.github/Dockerfile-release
vendored
|
@ -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
|
||||
|
|
2
.github/Dockerfile-release-debug
vendored
2
.github/Dockerfile-release-debug
vendored
|
@ -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
|
||||
|
|
2
.github/Dockerfile-release-debug-nonroot
vendored
2
.github/Dockerfile-release-debug-nonroot
vendored
|
@ -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
|
||||
|
|
2
.github/Dockerfile-release-nonroot
vendored
2
.github/Dockerfile-release-nonroot
vendored
|
@ -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
|
||||
|
|
6
.github/workflows/docker-main.yaml
vendored
6
.github/workflows/docker-main.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
|
@ -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
|
||||
|
||||
|
|
6
.github/workflows/test.yaml
vendored
6
.github/workflows/test.yaml
vendored
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pomerium
|
||||
spec:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pomerium
|
||||
spec:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pomerium
|
||||
spec:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pomerium
|
||||
spec:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pomerium
|
||||
spec:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,5 +3,6 @@ commonLabels:
|
|||
app.kubernetes.io/name: pomerium-zero
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- ./rbac
|
||||
- ./deployment
|
||||
- ./service
|
||||
|
|
6
k8s/zero/rbac/kustomization.yaml
Normal file
6
k8s/zero/rbac/kustomization.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- role.yaml
|
||||
- role_binding.yaml
|
||||
- service_account.yaml
|
14
k8s/zero/rbac/role.yaml
Normal file
14
k8s/zero/rbac/role.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: pomerium-zero
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- patch
|
||||
resourceNames:
|
||||
- pomerium
|
11
k8s/zero/rbac/role_binding.yaml
Normal file
11
k8s/zero/rbac/role_binding.yaml
Normal file
|
@ -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
|
4
k8s/zero/rbac/service_account.yaml
Normal file
4
k8s/zero/rbac/service_account.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: pomerium-zero
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
207
pkg/zero/cluster/client-with-responses.tmpl
Normal file
207
pkg/zero/cluster/client-with-responses.tmpl
Normal file
|
@ -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}}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue