Merge branch 'main' into kenjenkins/deprecate-jwt-endpoint

This commit is contained in:
Kenneth Jenkins 2024-09-04 09:56:13 -07:00
commit c36043f67f
43 changed files with 553 additions and 222 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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 \

View file

@ -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 \

View file

@ -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 organizations needs by integrating all data for [context-aware access](https://www.pomerium.com/context-aware-access).
Its not a VPN alternative its 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)!
Its not a VPN alternative its 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

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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),
)
}

View file

@ -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 &param, 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)

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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

View file

@ -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:

View file

@ -1,5 +1,5 @@
apiVersion: apps/v1
kind: StatefulSet
kind: Deployment
metadata:
name: pomerium
spec:

View file

@ -1,5 +1,5 @@
apiVersion: apps/v1
kind: StatefulSet
kind: Deployment
metadata:
name: pomerium
spec:

View file

@ -1,5 +1,5 @@
apiVersion: apps/v1
kind: StatefulSet
kind: Deployment
metadata:
name: pomerium
spec:

View file

@ -1,5 +1,5 @@
apiVersion: apps/v1
kind: StatefulSet
kind: Deployment
metadata:
name: pomerium
spec:

View file

@ -1,5 +1,5 @@
apiVersion: apps/v1
kind: StatefulSet
kind: Deployment
metadata:
name: pomerium
spec:

View file

@ -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

View file

@ -3,5 +3,6 @@ commonLabels:
app.kubernetes.io/name: pomerium-zero
resources:
- namespace.yaml
- ./rbac
- ./deployment
- ./service

View 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
View 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

View 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

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: pomerium-zero

View file

@ -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 {

View file

@ -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,
},
},
})

View 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}}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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(),

View file

@ -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
}

View file

@ -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()