Compare commits

...

13 commits

Author SHA1 Message Date
backport-actions-token[bot]
9f591c0ac0
authenticate: remove /.pomerium/callback handler (#5554)
authenticate: remove /.pomerium/callback handler (#5553)

Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
2025-03-28 13:38:27 -06:00
backport-actions-token[bot]
ed8c5d6999
ci: set goreleaser Node version to 22 (#5548)
ci: set goreleaser Node version to 22 (#5547)

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2025-03-26 13:39:45 -04:00
backport-actions-token[bot]
c0848eecfe
only support loading idp tokens via bearer tokens (#5546)
only support loading idp tokens via bearer tokens (#5545)

Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
2025-03-26 09:52:00 -06:00
backport-actions-token[bot]
618ab8fe3f
metrics: fix an apparent metric setup error (#5544)
metrics: fix an apparent metric setup error (#5543)

The IdentityManagerLastSessionRefreshErrorView appears to be a duplicate
of IdentityManagerLastUserRefreshErrorView. Adjust it to use the
matching identityManagerLastSessionRefreshError instead.

Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
2025-03-25 16:04:18 -07:00
Joe Kralicky
b139c4f425
[backport 0.29.0] move internal/telemetry/trace => pkg/telemetry/trace (#5542)
move internal/telemetry/trace => pkg/telemetry/trace
2025-03-25 12:54:46 -04:00
Denis Mishin
dcb10b1727
core/envoyconfig: make adding ipv6 addresses to internal cidr list conditional on ipv6 support on the system (#5538) (#5539) 2025-03-21 14:26:47 -04:00
backport-actions-token[bot]
839bedac80
authorize: return 403 on invalid sessions (#5537)
authorize: return 403 on invalid sessions (#5536)

Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
2025-03-19 14:43:03 -06:00
backport-actions-token[bot]
cc22174159
config: fix layered bearer_token_format and idp_access_token_allowed_audiences (#5534)
config: fix layered bearer_token_format and idp_access_token_allowed_audiences (#5533)

Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
2025-03-19 11:00:05 -06:00
backport-actions-token[bot]
a078f93986
metrics: reduce gc pressure (#5531)
metrics: reduce gc pressure (#5530)

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2025-03-18 14:06:08 -04:00
backport-actions-token[bot]
98a5779d77
websockets: disable http2 connect (#5527)
websockets: disable http2 connect (#5516)

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2025-03-14 13:06:34 -04:00
backport-actions-token[bot]
03064fae31
config: fix jwt_issuer_format conversion (#5525)
config: fix jwt_issuer_format conversion (#5524)

Remove the previous conversion logic in NewPolicyFromProto() for the 
jwt_issuer_format field. This would prevent the new "unset" state from
working correctly. Add a unit test to verify that all three values
(unset, "hostOnly" and "uri") will successfully round trip to the proto
format and back again.

Also add a test case for the Options.ApplySettings() method to verify 
that an unset jwt_issuer_format will not overwrite the existing value
(if any) in the settings.

Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
2025-03-12 16:58:15 -07:00
backport-actions-token[bot]
c8ad4c054d
add global jwt_issuer_format option (#5519)
add global jwt_issuer_format option (#5508)

Add a corresponding global setting for the existing route-level
jwt_issuer_format option. The route-level option will take precedence
when set to a non-empty string.

Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
2025-03-12 16:12:16 -07:00
backport-actions-token[bot]
e23caa50cf
zero/grpc: use hostname for proxied grpc calls (#5521)
zero/grpc: use hostname for proxied grpc calls (#5520)

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
2025-03-11 17:40:20 -04:00
99 changed files with 1530 additions and 1095 deletions

View file

@ -28,7 +28,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a
with:
node-version: 16.x
node-version: 22.x
- name: Set up Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34

View file

@ -10,8 +10,8 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
oteltrace "go.opentelemetry.io/otel/trace"
)

View file

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

View file

@ -694,14 +694,6 @@ func (m mockDataBrokerServiceClient) Put(ctx context.Context, in *databroker.Put
return m.put(ctx, in, opts...)
}
func mustParseURL(rawurl string) *url.URL {
u, err := url.Parse(rawurl)
if err != nil {
panic(err)
}
return u
}
// stubFlow is a stub implementation of the flow interface.
type stubFlow struct {
verifySignatureErr error

View file

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

View file

@ -19,10 +19,10 @@ import (
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Authorize struct holds
@ -149,6 +149,7 @@ func newPolicyEvaluator(
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
evaluator.WithJWTClaimsHeaders(opts.JWTClaimsHeaders),
evaluator.WithJWTGroupsFilter(opts.JWTGroupsFilter),
evaluator.WithDefaultJWTIssuerFormat(opts.JWTIssuerFormat),
)
}

View file

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

View file

@ -19,10 +19,10 @@ import (
"github.com/pomerium/pomerium/internal/errgrouputil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/contextutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/policy/criteria"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Request contains the inputs needed for evaluation.
@ -332,6 +332,7 @@ func updateStore(ctx context.Context, store *store.Store, cfg *evaluatorConfig)
)
store.UpdateJWTClaimHeaders(cfg.JWTClaimsHeaders)
store.UpdateJWTGroupsFilter(cfg.JWTGroupsFilter)
store.UpdateDefaultJWTIssuerFormat(cfg.DefaultJWTIssuerFormat)
store.UpdateRoutePolicies(cfg.Policies)
store.UpdateSigningKey(jwk)

View file

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

View file

@ -247,18 +247,16 @@ func (e *headersEvaluatorEvaluation) getGroupIDs(ctx context.Context) []string {
return make([]string, 0)
}
func (e *headersEvaluatorEvaluation) getJWTPayloadIss() (string, error) {
var issuerFormat string
if e.request.Policy != nil {
func (e *headersEvaluatorEvaluation) getJWTPayloadIss() string {
issuerFormat := e.evaluator.store.GetDefaultJWTIssuerFormat()
if e.request.Policy != nil && e.request.Policy.JWTIssuerFormat != "" {
issuerFormat = e.request.Policy.JWTIssuerFormat
}
switch issuerFormat {
case "uri":
return fmt.Sprintf("https://%s/", e.request.HTTP.Hostname), nil
case "", "hostOnly":
return e.request.HTTP.Hostname, nil
case config.JWTIssuerFormatURI:
return fmt.Sprintf("https://%s/", e.request.HTTP.Hostname)
default:
return "", fmt.Errorf("unsupported JWT issuer format: %s", issuerFormat)
return e.request.HTTP.Hostname
}
}
@ -412,14 +410,9 @@ func (e *headersEvaluatorEvaluation) getJWTPayload(ctx context.Context) (map[str
return e.cachedJWTPayload, nil
}
iss, err := e.getJWTPayloadIss()
if err != nil {
return nil, err
}
e.gotJWTPayload = true
e.cachedJWTPayload = map[string]any{
"iss": iss,
"iss": e.getJWTPayloadIss(),
"aud": e.getJWTPayloadAud(),
"jti": e.getJWTPayloadJTI(),
"iat": e.getJWTPayloadIAT(),

View file

@ -437,34 +437,59 @@ func TestHeadersEvaluator(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "u1@example.com", output.Headers.Get("X-Pomerium-Claim-Email"))
})
}
t.Run("issuer format", func(t *testing.T) {
t.Parallel()
func TestHeadersEvaluator_JWTIssuerFormat(t *testing.T) {
privateJWK, _ := newJWK(t)
for _, tc := range []struct {
format string
input string
output string
store := store.New()
store.UpdateSigningKey(privateJWK)
eval := func(_ *testing.T, input *Request) (*HeadersResponse, error) {
ctx := context.Background()
e := NewHeadersEvaluator(store)
return e.Evaluate(ctx, input)
}
hostname := "route.example.com"
cases := []struct {
globalFormat config.JWTIssuerFormat
routeFormat config.JWTIssuerFormat
expected string
}{
{"", "example.com", "example.com"},
{"hostOnly", "host-only.example.com", "host-only.example.com"},
{"uri", "uri.example.com", "https://uri.example.com/"},
} {
{"", "", "route.example.com"},
{"hostOnly", "", "route.example.com"},
{"uri", "", "https://route.example.com/"},
{"", "hostOnly", "route.example.com"},
{"hostOnly", "hostOnly", "route.example.com"},
{"uri", "hostOnly", "route.example.com"},
{"", "uri", "https://route.example.com/"},
{"hostOnly", "uri", "https://route.example.com/"},
{"uri", "uri", "https://route.example.com/"},
}
for _, tc := range cases {
t.Run("", func(t *testing.T) {
store.UpdateDefaultJWTIssuerFormat(tc.globalFormat)
output, err := eval(t,
nil,
&Request{
HTTP: RequestHTTP{
Hostname: tc.input,
Hostname: hostname,
},
Policy: &config.Policy{
JWTIssuerFormat: tc.format,
JWTIssuerFormat: tc.routeFormat,
},
})
require.NoError(t, err)
m := decodeJWTAssertion(t, output.Headers)
assert.Equal(t, tc.output, m["iss"], "unexpected issuer for format=%s", tc.format)
}
assert.Equal(t, tc.expected, m["iss"],
"unexpected issuer for global format=%q, route format=%q",
tc.globalFormat, tc.routeFormat)
})
}
}
func TestHeadersEvaluator_JWTGroupsFilter(t *testing.T) {

View file

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

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
"net/url"
@ -54,8 +55,11 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
// load the session
s, err := a.loadSession(ctx, hreq, req)
if err != nil {
return nil, err
if errors.Is(err, sessions.ErrInvalidSession) {
// ENG-2172: if this is an invalid session, don't evaluate policy, return forbidden
return a.deniedResponse(ctx, in, int32(http.StatusForbidden), http.StatusText(http.StatusForbidden), nil)
} else if err != nil {
return nil, fmt.Errorf("error loading session: %w", err)
}
// if there's a session or service account, load the user
@ -122,6 +126,7 @@ func (a *Authorize) loadSession(
Str("request-id", requestID).
Err(err).
Msg("error creating session for incoming idp token")
return nil, err
}
sessionState, _ := a.state.Load().sessionStore.LoadSessionStateAndCheckIDP(hreq)

View file

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

View file

@ -12,13 +12,13 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/version"
_ "github.com/pomerium/pomerium/internal/zero/bootstrap/writers/filesystem"
_ "github.com/pomerium/pomerium/internal/zero/bootstrap/writers/k8s"
zero_cmd "github.com/pomerium/pomerium/internal/zero/cmd"
"github.com/pomerium/pomerium/pkg/cmd/pomerium"
"github.com/pomerium/pomerium/pkg/envoy/files"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
func main() {

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/otelconfig"
"github.com/pomerium/pomerium/internal/telemetry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
)

View file

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

View file

@ -12,6 +12,7 @@ type Builder struct {
localMetricsAddress string
filemgr *filemgr.Manager
reproxy *reproxy.Handler
addIPV6InternalRanges bool
}
// New creates a new Builder.
@ -21,6 +22,7 @@ func New(
localMetricsAddress string,
fileManager *filemgr.Manager,
reproxyHandler *reproxy.Handler,
addIPV6InternalRanges bool,
) *Builder {
if reproxyHandler == nil {
reproxyHandler = reproxy.New()
@ -31,5 +33,6 @@ func New(
localMetricsAddress: localMetricsAddress,
filemgr: fileManager,
reproxy: reproxyHandler,
addIPV6InternalRanges: addIPV6InternalRanges,
}
}

View file

@ -20,8 +20,8 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// BuildClusters builds envoy clusters from the given config.

View file

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

View file

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

View file

@ -128,23 +128,29 @@ func (b *Builder) buildLocalReplyConfig(
}, nil
}
func applyGlobalHTTPConnectionManagerOptions(hcm *envoy_http_connection_manager.HttpConnectionManager) {
func (b *Builder) applyGlobalHTTPConnectionManagerOptions(hcm *envoy_http_connection_manager.HttpConnectionManager) {
if hcm.InternalAddressConfig == nil {
// see doc comment on InternalAddressConfig for details
hcm.InternalAddressConfig = &envoy_http_connection_manager.HttpConnectionManager_InternalAddressConfig{
CidrRanges: []*envoy_config_core_v3.CidrRange{
ranges := []*envoy_config_core_v3.CidrRange{
// localhost
{AddressPrefix: "127.0.0.1", PrefixLen: wrapperspb.UInt32(32)},
{AddressPrefix: "::1", PrefixLen: wrapperspb.UInt32(128)},
// RFC1918
{AddressPrefix: "10.0.0.0", PrefixLen: wrapperspb.UInt32(8)},
{AddressPrefix: "192.168.0.0", PrefixLen: wrapperspb.UInt32(16)},
{AddressPrefix: "172.16.0.0", PrefixLen: wrapperspb.UInt32(12)},
}
if b.addIPV6InternalRanges {
ranges = append(ranges, []*envoy_config_core_v3.CidrRange{
// Localhost IPv6
{AddressPrefix: "::1", PrefixLen: wrapperspb.UInt32(128)},
// RFC4193
{AddressPrefix: "fd00::", PrefixLen: wrapperspb.UInt32(8)},
},
}...)
}
// see doc comment on InternalAddressConfig for details
hcm.InternalAddressConfig = &envoy_http_connection_manager.HttpConnectionManager_InternalAddressConfig{
CidrRanges: ranges,
}
}
}

View file

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

View file

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

View file

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

View file

@ -207,9 +207,6 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
mgr.CodecType = envoy_extensions_filters_network_http_connection_manager.HttpConnectionManager_AUTO
} else if cfg.Options.GetCodecType() == config.CodecTypeAuto || cfg.Options.GetCodecType() == config.CodecTypeHTTP2 {
mgr.CodecType = cfg.Options.GetCodecType().ToEnvoy()
mgr.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{
AllowConnect: true,
}
} else {
mgr.CodecType = cfg.Options.GetCodecType().ToEnvoy()
}
@ -236,7 +233,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
}
}
return HTTPConnectionManagerFilter(mgr), nil
return b.HTTPConnectionManagerFilter(mgr), nil
}
func newListenerAccessLog() *envoy_config_accesslog_v3.AccessLog {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,8 +11,8 @@ import (
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// BuildRouteConfigurations builds the route configurations for the RDS service.

View file

@ -32,7 +32,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
},
},
}}
b := New("grpc", "http", "metrics", filemgr.NewManager(), nil)
b := New("grpc", "http", "metrics", filemgr.NewManager(), nil, true)
routeConfiguration, err := b.buildMainRouteConfiguration(ctx, cfg)
assert.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `{

View file

@ -113,9 +113,6 @@
}
}
},
"http2ProtocolOptions": {
"allowConnect": true
},
"localReplyConfig": {
"mappers": [
{
@ -234,10 +231,6 @@
"addressPrefix": "127.0.0.1",
"prefixLen": 32
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "10.0.0.0",
"prefixLen": 8
@ -250,6 +243,10 @@
"addressPrefix": "172.16.0.0",
"prefixLen": 12
},
{
"addressPrefix": "::1",
"prefixLen": 128
},
{
"addressPrefix": "fd00::",
"prefixLen": 8

View file

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

View file

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

View file

@ -17,7 +17,7 @@ import (
extensions_uuidx "github.com/pomerium/envoy-custom/api/extensions/request_id/uuidx"
extensions_pomerium_otel "github.com/pomerium/envoy-custom/api/extensions/tracers/pomerium_otel"
"github.com/pomerium/pomerium/config/otelconfig"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)

View file

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

View file

@ -196,6 +196,12 @@ type Options struct {
// List of JWT claims to insert as x-pomerium-claim-* headers on proxied requests
JWTClaimsHeaders JWTClaimHeaders `mapstructure:"jwt_claims_headers" yaml:"jwt_claims_headers,omitempty"`
// JWTIssuerFormat controls the default format of the 'iss' claim in JWTs passed to upstream services.
// Possible values:
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
// - "uri": Issuer strings will be a complete URI, including the scheme and ending with a trailing slash.
JWTIssuerFormat JWTIssuerFormat `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
// BearerTokenFormat indicates how authorization bearer tokens are interepreted. Possible values:
// - "default": Only Bearer tokens prefixed with Pomerium- will be interpreted by Pomerium.
// - "idp_access_token": The Bearer token will be interpreted as an IdP access token.
@ -761,6 +767,10 @@ func (o *Options) Validate() error {
}
}
if !o.JWTIssuerFormat.Valid() {
return fmt.Errorf("config: unsupported jwt_issuer_format value %q", o.JWTIssuerFormat)
}
return nil
}
@ -1499,8 +1509,6 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
if settings.IdpAccessTokenAllowedAudiences != nil {
values := slices.Clone(settings.IdpAccessTokenAllowedAudiences.Values)
o.IDPAccessTokenAllowedAudiences = &values
} else {
o.IDPAccessTokenAllowedAudiences = nil
}
setSlice(&o.AuthorizeURLStrings, settings.AuthorizeServiceUrls)
set(&o.AuthorizeInternalURLString, settings.AuthorizeInternalServiceUrl)
@ -1510,10 +1518,13 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
set(&o.SigningKey, settings.SigningKey)
setMap(&o.SetResponseHeaders, settings.SetResponseHeaders)
setMap(&o.JWTClaimsHeaders, settings.JwtClaimsHeaders)
o.BearerTokenFormat = BearerTokenFormatFromPB(settings.BearerTokenFormat)
setOptional(&o.BearerTokenFormat, BearerTokenFormatFromPB(settings.BearerTokenFormat))
if len(settings.JwtGroupsFilter) > 0 {
o.JWTGroupsFilter = NewJWTGroupsFilter(settings.JwtGroupsFilter)
}
if f := JWTIssuerFormatFromPB(settings.JwtIssuerFormat); f != JWTIssuerFormatUnset {
o.JWTIssuerFormat = f
}
setDuration(&o.DefaultUpstreamTimeout, settings.DefaultUpstreamTimeout)
set(&o.MetricsAddr, settings.MetricsAddress)
set(&o.MetricsBasicAuth, settings.MetricsBasicAuth)
@ -1624,6 +1635,7 @@ func (o *Options) ToProto() *config.Config {
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
settings.BearerTokenFormat = o.BearerTokenFormat.ToPB()
settings.JwtGroupsFilter = o.JWTGroupsFilter.ToSlice()
settings.JwtIssuerFormat = o.JWTIssuerFormat.ToPB()
copyOptionalDuration(&settings.DefaultUpstreamTimeout, o.DefaultUpstreamTimeout)
copySrcToOptionalDest(&settings.MetricsAddress, &o.MetricsAddr)
copySrcToOptionalDest(&settings.MetricsBasicAuth, &o.MetricsBasicAuth)

View file

@ -924,6 +924,8 @@ func TestOptions_GetAllRouteableHTTPHosts(t *testing.T) {
}
func TestOptions_ApplySettings(t *testing.T) {
t.Parallel()
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second)
defer clearTimeout()
@ -989,6 +991,48 @@ func TestOptions_ApplySettings(t *testing.T) {
})
assert.Equal(t, NewJWTGroupsFilter([]string{"quux", "zulu"}), options.JWTGroupsFilter)
})
t.Run("jwt_issuer_format", func(t *testing.T) {
options := NewDefaultOptions()
assert.Equal(t, JWTIssuerFormatUnset, options.JWTIssuerFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
JwtIssuerFormat: configpb.IssuerFormat_IssuerURI.Enum(),
})
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, JWTIssuerFormatURI, options.JWTIssuerFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
JwtIssuerFormat: configpb.IssuerFormat_IssuerHostOnly.Enum(),
})
assert.Equal(t, JWTIssuerFormatHostOnly, options.JWTIssuerFormat)
})
t.Run("bearer_token_format", func(t *testing.T) {
t.Parallel()
options := NewDefaultOptions()
assert.Nil(t, options.BearerTokenFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{
BearerTokenFormat: configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_DEFAULT.Enum(),
})
assert.Equal(t, ptr(BearerTokenFormatDefault), options.BearerTokenFormat)
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, ptr(BearerTokenFormatDefault), options.BearerTokenFormat, "should preserve existing bearer token format")
})
t.Run("idp_access_token_allowed_audiences", func(t *testing.T) {
t.Parallel()
options := NewDefaultOptions()
assert.Nil(t, options.IDPAccessTokenAllowedAudiences)
options.ApplySettings(ctx, nil, &configpb.Settings{
IdpAccessTokenAllowedAudiences: &configpb.Settings_StringList{Values: []string{"x", "y", "z"}},
})
assert.Equal(t, ptr([]string{"x", "y", "z"}), options.IDPAccessTokenAllowedAudiences)
options.ApplySettings(ctx, nil, &configpb.Settings{})
assert.Equal(t, ptr([]string{"x", "y", "z"}), options.IDPAccessTokenAllowedAudiences,
"should preserve idp access token allowed audiences")
})
}
func TestOptions_GetSetResponseHeaders(t *testing.T) {
@ -1748,3 +1792,7 @@ func must[T any](t T, err error) T {
}
return t
}
func ptr[T any](v T) *T {
return &v
}

View file

@ -167,7 +167,7 @@ type Policy struct {
// Possible values:
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
// - "uri": Issuer strings will be a complete URI, including the scheme and ending with a trailing slash.
JWTIssuerFormat string `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
JWTIssuerFormat JWTIssuerFormat `mapstructure:"jwt_issuer_format" yaml:"jwt_issuer_format,omitempty"`
// BearerTokenFormat indicates how authorization bearer tokens are interepreted. Possible values:
// - "default": Only Bearer tokens prefixed with Pomerium- will be interpreted by Pomerium
// - "idp_access_token": The Bearer token will be interpreted as an IdP access token.
@ -309,6 +309,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
IDPClientID: pb.GetIdpClientId(),
IDPClientSecret: pb.GetIdpClientSecret(),
JWTGroupsFilter: NewJWTGroupsFilter(pb.JwtGroupsFilter),
JWTIssuerFormat: JWTIssuerFormatFromPB(pb.JwtIssuerFormat),
KubernetesServiceAccountToken: pb.GetKubernetesServiceAccountToken(),
KubernetesServiceAccountTokenFile: pb.GetKubernetesServiceAccountTokenFile(),
LogoURL: pb.GetLogoUrl(),
@ -388,13 +389,6 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
p.EnvoyOpts.Name = pb.Name
}
switch pb.GetJwtIssuerFormat() {
case configpb.IssuerFormat_IssuerHostOnly:
p.JWTIssuerFormat = "hostOnly"
case configpb.IssuerFormat_IssuerURI:
p.JWTIssuerFormat = "uri"
}
p.BearerTokenFormat = BearerTokenFormatFromPB(pb.BearerTokenFormat)
for _, rwh := range pb.RewriteResponseHeaders {
@ -468,6 +462,7 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
Id: p.ID,
IdleTimeout: idleTimeout,
JwtGroupsFilter: p.JWTGroupsFilter.ToSlice(),
JwtIssuerFormat: p.JWTIssuerFormat.ToPB(),
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
KubernetesServiceAccountTokenFile: p.KubernetesServiceAccountTokenFile,
LogoUrl: p.LogoURL,
@ -555,13 +550,6 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
pb.LoadBalancingWeights = weights
}
switch p.JWTIssuerFormat {
case "", "hostOnly":
pb.JwtIssuerFormat = configpb.IssuerFormat_IssuerHostOnly
case "uri":
pb.JwtIssuerFormat = configpb.IssuerFormat_IssuerURI
}
pb.BearerTokenFormat = p.BearerTokenFormat.ToPB()
for _, rwh := range p.RewriteResponseHeaders {
@ -698,6 +686,10 @@ func (p *Policy) Validate() error {
p.compiledRegex, _ = regexp.Compile(rawRE)
}
if !p.JWTIssuerFormat.Valid() {
return fmt.Errorf("config: unsupported jwt_issuer_format value %q", p.JWTIssuerFormat)
}
return nil
}

View file

@ -292,6 +292,22 @@ func TestPolicy_FromToPb(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, p.Redirect.HTTPSRedirect, policyFromProto.Redirect.HTTPSRedirect)
})
t.Run("JWT issuer format", func(t *testing.T) {
for f := range knownJWTIssuerFormats {
p := &Policy{
From: "https://pomerium.io",
To: mustParseWeightedURLs(t, "http://localhost"),
JWTIssuerFormat: f,
}
pbPolicy, err := p.ToProto()
require.NoError(t, err)
policyFromPb, err := NewPolicyFromProto(pbPolicy)
assert.NoError(t, err)
assert.Equal(t, f, policyFromPb.JWTIssuerFormat)
}
})
}
func TestPolicy_Matches(t *testing.T) {

View file

@ -25,6 +25,9 @@ var (
// is deprecated pending removal in a future release, but this flag allows a temporary
// opt-out from the deprecation.
RuntimeFlagPomeriumJWTEndpoint = runtimeFlag("pomerium_jwt_endpoint", false)
// RuntimeFlagAddExtraMetricsLabels enables adding extra labels to metrics (host and installation id)
RuntimeFlagAddExtraMetricsLabels = runtimeFlag("add_extra_metrics_labels", true)
)
// RuntimeFlag is a runtime flag that can flip on/off certain features

View file

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

View file

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

View file

@ -19,7 +19,6 @@ import (
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/events"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/envoy/files"
@ -29,6 +28,7 @@ import (
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/legacymanager"
"github.com/pomerium/pomerium/pkg/identity/manager"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
oteltrace "go.opentelemetry.io/otel/trace"
)

14
go.mod
View file

@ -88,15 +88,15 @@ require (
go.uber.org/automaxprocs v1.6.0
go.uber.org/mock v0.5.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.33.0
golang.org/x/net v0.35.0
golang.org/x/crypto v0.36.0
golang.org/x/net v0.37.0
golang.org/x/oauth2 v0.27.0
golang.org/x/sync v0.11.0
golang.org/x/sys v0.30.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/time v0.10.0
google.golang.org/api v0.223.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2
google.golang.org/grpc v1.70.0
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
gopkg.in/yaml.v3 v3.0.1
sigs.k8s.io/yaml v1.4.0
@ -230,7 +230,7 @@ require (
github.com/zeebo/assert v1.3.1 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.34.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.34.0 // indirect
@ -239,7 +239,7 @@ require (
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect

32
go.sum
View file

@ -690,8 +690,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
@ -750,8 +750,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -827,8 +827,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -851,8 +851,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -912,15 +912,15 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -931,8 +931,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1061,8 +1061,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

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

View file

@ -23,7 +23,6 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -33,6 +32,7 @@ import (
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/manager"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Stateful implements the stateful authentication flow. In this flow, the

View file

@ -21,7 +21,6 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
@ -31,6 +30,7 @@ import (
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/hpke"
"github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"go.opentelemetry.io/otel"
oteltrace "go.opentelemetry.io/otel/trace"

View file

@ -17,10 +17,10 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/middleware"
"github.com/pomerium/pomerium/internal/telemetry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
hpke_handlers "github.com/pomerium/pomerium/pkg/hpke/handlers"
"github.com/pomerium/pomerium/pkg/telemetry/requestid"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
func (srv *Server) addHTTPMiddleware(ctx context.Context, root *mux.Router, _ *config.Config) {

View file

@ -13,6 +13,7 @@ import (
"github.com/rs/zerolog"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
"golang.org/x/net/nettest"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
@ -27,7 +28,6 @@ import (
"github.com/pomerium/pomerium/internal/events"
"github.com/pomerium/pomerium/internal/httputil/reproxy"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/envoy/files"
@ -35,6 +35,7 @@ import (
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/httputil"
"github.com/pomerium/pomerium/pkg/telemetry/requestid"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
oteltrace "go.opentelemetry.io/otel/trace"
)
@ -177,6 +178,7 @@ func NewServer(
srv.MetricsListener.Addr().String(),
srv.filemgr,
srv.reproxy,
nettest.SupportsIPv6(),
)
res, err := srv.buildDiscoveryResources(ctx)

View file

@ -15,13 +15,13 @@ import (
"github.com/pomerium/pomerium/internal/hashutil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/health"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
oteltrace "go.opentelemetry.io/otel/trace"
googlegrpc "google.golang.org/grpc"

View file

@ -17,11 +17,11 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/registry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/storage"
"github.com/pomerium/pomerium/pkg/storage/inmemory"
"github.com/pomerium/pomerium/pkg/storage/postgres"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
oteltrace "go.opentelemetry.io/otel/trace"
)

View file

@ -22,8 +22,6 @@ const (
// can be used in place of the standard authorization header if that header is being
// used by upstream applications.
HeaderPomeriumAuthorization = "x-pomerium-authorization"
HeaderPomeriumIDPAccessToken = "x-pomerium-idp-access-token" //nolint: gosec
HeaderPomeriumIDPIdentityToken = "x-pomerium-idp-identity-token" //nolint: gosec
// HeaderPomeriumResponse is set when pomerium itself creates a response,
// as opposed to the upstream application and can be used to distinguish
// between an application error, and a pomerium related error when debugging.

View file

@ -8,6 +8,9 @@ var (
// ErrNoSessionFound is the error for when no session is found.
ErrNoSessionFound = errors.New("internal/sessions: session is not found")
// ErrInvalidSession is the error for when a session is invalid.
ErrInvalidSession = errors.New("internal/sessions: invalid session")
// ErrMalformed is the error for when a session is found but is malformed.
ErrMalformed = errors.New("internal/sessions: session is malformed")

View file

@ -0,0 +1,115 @@
package metrics_test
import (
"fmt"
"io"
"net/http"
"sort"
"testing"
"time"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/upstreams"
"github.com/stretchr/testify/assert"
)
func TestScrapeMetricsEndpoint(t *testing.T) {
t.Skip("this test is for profiling purposes only")
env := testenv.New(t, testenv.WithTraceDebugFlags(testenv.StandardTraceDebugFlags))
upstream := upstreams.HTTP(nil)
upstream.Handle("/test", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("OK"))
})
routes := []testenv.Route{}
for i := range 10 {
routes = append(routes, upstream.Route().
From(env.SubdomainURL(fmt.Sprintf("test-%d", i))).
Policy(func(p *config.Policy) { p.AllowPublicUnauthenticatedAccess = true }))
}
env.AddUpstream(upstream)
env.Start()
snippets.WaitStartupComplete(env)
for _, r := range routes {
resp, err := upstream.Get(r, upstreams.Path("/test"))
assert.NoError(t, err)
data, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
resp.Body.Close()
assert.Equal(t, "OK", string(data))
}
metricsURL := fmt.Sprintf("http://localhost:%d/metrics", env.Ports().Metrics.Value())
client := &http.Client{
Timeout: 10 * time.Second,
}
var durations []time.Duration
var totalBytes int64
var errors int
pct := 0
niter := 200
for i := 0; i < niter; i++ {
pct = i * 100 / niter
if pct%10 == 0 {
t.Log(pct, "%")
}
start := time.Now()
resp, err := client.Get(metricsURL)
elapsed := time.Since(start)
if err != nil {
t.Logf("Request %d failed: %v", i, err)
errors++
continue
}
nb, err := io.Copy(io.Discard, resp.Body)
if err != nil {
resp.Body.Close()
errors++
continue
}
resp.Body.Close()
durations = append(durations, elapsed)
totalBytes += nb
}
if len(durations) > 0 {
sort.Slice(durations, func(i, j int) bool {
return durations[i] < durations[j]
})
var total time.Duration
for _, d := range durations {
total += d
}
t.Logf("Metrics scraping statistics:")
t.Logf(" Successful requests: %d", len(durations))
t.Logf(" Failed requests: %d", errors)
t.Logf(" Total bytes: %d", totalBytes)
t.Logf(" Avg bytes per request: %.2f", float64(totalBytes)/float64(len(durations)))
t.Logf(" Min: %v", durations[0])
t.Logf(" Max: %v", durations[len(durations)-1])
t.Logf(" Avg: %v", total/time.Duration(len(durations)))
t.Logf(" p50: %v", durations[len(durations)*50/100])
t.Logf(" p90: %v", durations[len(durations)*90/100])
t.Logf(" p95: %v", durations[len(durations)*95/100])
t.Logf(" p99: %v", durations[len(durations)*99/100])
} else {
t.Logf("No successful requests made")
}
t.Logf("metrics endpoint: %s", metricsURL)
env.Stop()
}

View file

@ -240,11 +240,11 @@ var (
Measure: identityManagerLastSessionRefreshSuccess,
Aggregation: view.Count(),
}
// IdentityManagerLastSessionRefreshErrorView contains user refresh errors counter
// IdentityManagerLastSessionRefreshErrorView contains session refresh errors counter
IdentityManagerLastSessionRefreshErrorView = &view.View{
Name: identityManagerLastUserRefreshError.Name(),
Description: identityManagerLastUserRefreshError.Description(),
Measure: identityManagerLastUserRefreshError,
Name: identityManagerLastSessionRefreshError.Name(),
Description: identityManagerLastSessionRefreshError.Description(),
Measure: identityManagerLastSessionRefreshError,
Aggregation: view.Count(),
}
// IdentityManagerLastSessionRefreshSuccessTimestampView contains successful session refresh counter

View file

@ -10,7 +10,6 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"sync"
"time"
@ -22,7 +21,6 @@ import (
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/prometheus"
"github.com/pomerium/pomerium/pkg/metrics"
)
// EnvoyMetricsPath is the path on the metrics listener that retrieves envoy metrics.
@ -44,7 +42,7 @@ func (e *ScrapeEndpoint) String() string {
// PrometheusHandler creates an exporter that exports stats to Prometheus
// and returns a handler suitable for exporting metrics.
func PrometheusHandler(endpoints []ScrapeEndpoint, installationID string, timeout time.Duration) (http.Handler, error) {
func PrometheusHandler(endpoints []ScrapeEndpoint, timeout time.Duration, labels map[string]string) (http.Handler, error) {
exporter, err := getGlobalExporter()
if err != nil {
return nil, err
@ -52,7 +50,7 @@ func PrometheusHandler(endpoints []ScrapeEndpoint, installationID string, timeou
mux := http.NewServeMux()
mux.Handle("/metrics", newProxyMetricsHandler(exporter, endpoints, installationID, timeout))
mux.Handle("/metrics", newProxyMetricsHandler(exporter, endpoints, timeout, labels))
return mux, nil
}
@ -96,12 +94,16 @@ func registerDefaultViews() error {
// newProxyMetricsHandler creates a subrequest to the envoy control plane for metrics and
// combines them with internal envoy-provided
func newProxyMetricsHandler(exporter *ocprom.Exporter, endpoints []ScrapeEndpoint, installationID string, timeout time.Duration) http.HandlerFunc {
func newProxyMetricsHandler(
exporter *ocprom.Exporter,
endpoints []ScrapeEndpoint,
timeout time.Duration,
labels map[string]string,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
labels := getCommonLabels(installationID)
if err := writeMetricsMux(ctx, w, append(
scrapeEndpoints(endpoints, labels),
ocExport("pomerium", exporter, r, labels)),
@ -156,7 +158,7 @@ func writeMetricsResult(w io.Writer, res promProducerResult) error {
return fmt.Errorf("fetch: %w", res.err)
}
return errors.Join(
prometheus.Export(w, prometheus.AddLabels(prometheus.NewMetricFamilyStream(res.src), res.labels)),
prometheus.RelabelTextStream(w, res.src, res.labels),
res.src.Close(),
)
}
@ -229,17 +231,3 @@ func scrapeEndpoint(endpoint ScrapeEndpoint, extra map[string]string) promProduc
}
}
}
func getCommonLabels(installationID string) map[string]string {
hostname, err := os.Hostname()
if err != nil {
hostname = "__none__"
}
m := map[string]string{
metrics.HostnameLabel: hostname,
}
if installationID != "" {
m[metrics.InstallationIDLabel] = installationID
}
return m
}

View file

@ -29,7 +29,7 @@ envoy_server_initialization_time_ms_bucket{le="1000"} 1
}
func getMetrics(t *testing.T, envoyURL *url.URL, header http.Header) []byte {
h, err := PrometheusHandler([]ScrapeEndpoint{{Name: "envoy", URL: *envoyURL}}, "test_installation_id", time.Second*20)
h, err := PrometheusHandler([]ScrapeEndpoint{{Name: "envoy", URL: *envoyURL}}, time.Second*20, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -0,0 +1,103 @@
package prometheus
import (
"bufio"
"bytes"
"errors"
"io"
"maps"
"slices"
"strings"
)
func writeMulti(dst io.Writer, b ...[]byte) error {
for _, buf := range b {
if _, err := dst.Write(buf); err != nil {
return err
}
}
return nil
}
// RelabelTextStream relabels a prometheus text stream by adding additional labels to each metric.
func RelabelTextStream(dst io.Writer, src io.Reader, addLabels map[string]string) error {
if len(addLabels) == 0 {
_, err := io.Copy(dst, src)
return err
}
var labelsBuilder strings.Builder
for _, k := range slices.Sorted(maps.Keys(addLabels)) {
v := addLabels[k]
if labelsBuilder.Len() > 0 {
labelsBuilder.WriteByte(',')
}
labelsBuilder.WriteString(k)
labelsBuilder.WriteString("=\"")
labelsBuilder.WriteString(v)
labelsBuilder.WriteString("\"")
}
addedLabels := []byte(labelsBuilder.String())
r := bufio.NewReader(src)
for {
line, err := r.ReadSlice('\n')
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
if len(line) == 0 || line[0] == '#' {
if _, err := dst.Write(line); err != nil {
return err
}
continue
}
spaceIdx := bytes.IndexByte(line, ' ')
if spaceIdx == -1 {
if _, err := dst.Write(line); err != nil {
return err
}
continue
}
metricWithLabels := line[:spaceIdx]
value := line[spaceIdx:]
openBraceIdx := bytes.IndexByte(metricWithLabels, '{')
if openBraceIdx == -1 { // no labels
if err := writeMulti(dst, metricWithLabels, []byte("{"), addedLabels, []byte("}"), value); err != nil {
return err
}
continue
}
metricName := metricWithLabels[:openBraceIdx]
closeBraceIdx := bytes.LastIndexByte(metricWithLabels, '}')
if closeBraceIdx == -1 || closeBraceIdx <= openBraceIdx {
if _, err := dst.Write(line); err != nil {
return err
}
continue
}
existingLabels := metricWithLabels[openBraceIdx+1 : closeBraceIdx]
if len(existingLabels) > 0 {
if err := writeMulti(dst, metricName, []byte("{"), existingLabels, []byte(","), addedLabels, []byte("}"), value); err != nil {
return err
}
} else {
if err := writeMulti(dst, metricName, []byte("{"), addedLabels, []byte("}"), value); err != nil {
return err
}
}
}
return nil
}

View file

@ -0,0 +1,141 @@
package prometheus_test
import (
"bytes"
"io"
"strings"
"testing"
"github.com/pomerium/pomerium/internal/telemetry/prometheus"
"github.com/stretchr/testify/require"
)
// RepeatingReader repeats reading from the beginning after EOF for a specified number of times
type RepeatingReader struct {
reader *bytes.Reader
resets int
maxResets int
}
// NewRepeatingReader creates a new reader that will reset up to maxResets times
func NewRepeatingReader(data []byte, maxResets int) *RepeatingReader {
return &RepeatingReader{
reader: bytes.NewReader(data),
resets: 0,
maxResets: maxResets,
}
}
// Read implements io.Reader
func (r *RepeatingReader) Read(p []byte) (n int, err error) {
n, err = r.reader.Read(p)
if err == io.EOF && r.resets < r.maxResets {
r.reader.Seek(0, io.SeekStart)
r.resets++
return r.Read(p)
}
return
}
func BenchmarkRelabelTestStream(b *testing.B) {
addLabels := map[string]string{"instance": "localhost:9090", "installation_id": "12345-67890-12345-67890"}
src := []byte(`
# TYPE envoy_cluster_upstream_cx_total counter
envoy_cluster_upstream_cx_total{service="pomerium-proxy",envoy_cluster_name="route-1",installation_id="aecd6525-9eaa-448d-93d9-6363c04b1ccb",hostname="pomerium-proxy-55589cc5f-fjhsb"} 2
envoy_cluster_upstream_cx_total{service="pomerium-proxy",envoy_cluster_name="route-2",installation_id="aecd6525-9eaa-448d-93d9-6363c04b1ccb",hostname="pomerium-proxy-55589cc5f-fjhsb"} 3
`)
b.Run("RelabelTextStream", func(b *testing.B) {
inputReader := NewRepeatingReader(src, b.N)
err := prometheus.RelabelTextStream(io.Discard, inputReader, addLabels)
require.NoError(b, err)
})
b.Run("Previous", func(b *testing.B) {
inputReader := NewRepeatingReader(src, b.N)
err := prometheus.Export(io.Discard, prometheus.AddLabels(prometheus.NewMetricFamilyStream(inputReader), addLabels))
require.NoError(b, err)
})
}
func TestRelabelTextStream(t *testing.T) {
testCases := []struct {
name string
input string
addLabels map[string]string
expected string
}{
{
name: "empty input",
input: "",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "",
},
{
name: "no labels to add",
input: "metric 42\n",
addLabels: map[string]string{},
expected: "metric 42\n",
},
{
name: "comment lines",
input: "# HELP metric_name Help text\n# TYPE metric_name counter\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "# HELP metric_name Help text\n# TYPE metric_name counter\n",
},
{
name: "metric without labels",
input: "http_requests_total 42\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "http_requests_total{instance=\"localhost:9090\"} 42\n",
},
{
name: "metric with existing labels",
input: "http_requests_total{method=\"GET\"} 42\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "http_requests_total{method=\"GET\",instance=\"localhost:9090\"} 42\n",
},
{
name: "multiple labels to add",
input: "http_requests_total 42\n",
addLabels: map[string]string{"instance": "localhost:9090", "job": "prometheus"},
expected: "http_requests_total{instance=\"localhost:9090\",job=\"prometheus\"} 42\n",
},
{
name: "malformed metric (no space)",
input: "invalid_metric\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "invalid_metric\n",
},
{
name: "malformed metric (no closing brace)",
input: "invalid_metric{label=\"value\" 42\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "invalid_metric{label=\"value\" 42\n",
},
{
name: "empty labels",
input: "http_requests_total{} 42\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "http_requests_total{instance=\"localhost:9090\"} 42\n",
},
{
name: "multiple metrics",
input: "metric1 10\nmetric2{label=\"value\"} 20\n# COMMENT\nmetric3 30\n",
addLabels: map[string]string{"instance": "localhost:9090"},
expected: "metric1{instance=\"localhost:9090\"} 10\nmetric2{label=\"value\",instance=\"localhost:9090\"} 20\n# COMMENT\nmetric3{instance=\"localhost:9090\"} 30\n",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
inputReader := strings.NewReader(tc.input)
outputBuffer := &bytes.Buffer{}
err := prometheus.RelabelTextStream(outputBuffer, inputReader, tc.addLabels)
require.NoError(t, err)
actual := outputBuffer.String()
require.Equal(t, tc.expected, actual)
})
}
}

View file

@ -36,7 +36,6 @@ import (
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
databroker_service "github.com/pomerium/pomerium/databroker"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv/envutil"
"github.com/pomerium/pomerium/internal/testenv/values"
"github.com/pomerium/pomerium/pkg/cmd/pomerium"
@ -47,6 +46,7 @@ import (
"github.com/pomerium/pomerium/pkg/identity/manager"
"github.com/pomerium/pomerium/pkg/netutil"
"github.com/pomerium/pomerium/pkg/slices"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

View file

@ -21,12 +21,12 @@ import (
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/scenarios"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/upstreams"
. "github.com/pomerium/pomerium/internal/testutil/tracetest" //nolint:revive
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
var allServices = []string{

View file

@ -4,9 +4,9 @@ import (
"context"
"time"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/pkg/grpcutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"

View file

@ -6,10 +6,10 @@ import (
"net"
"strings"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/values"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
oteltrace "go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"

View file

@ -21,10 +21,10 @@ import (
"github.com/gorilla/mux"
"github.com/pomerium/pomerium/integration/forms"
"github.com/pomerium/pomerium/internal/retry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/snippets"
"github.com/pomerium/pomerium/internal/testenv/values"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"

View file

@ -6,7 +6,7 @@ import (
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
oteltrace "go.opentelemetry.io/otel/trace"

View file

@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
oteltrace "go.opentelemetry.io/otel/trace"

View file

@ -16,7 +16,7 @@ import (
"unique"
gocmp "github.com/google/go-cmp/cmp"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
oteltrace "go.opentelemetry.io/otel/trace"

View file

@ -48,6 +48,7 @@ func New(
func (c *client) getGRPCConn(ctx context.Context) (*grpc.ClientConn, error) {
opts := append(
c.config.GetDialOptions(),
grpc.WithAuthority(c.config.GetAuthority()),
grpc.WithPerRPCCredentials(c),
grpc.WithDefaultCallOptions(
grpc.UseCompressor("gzip"),
@ -60,7 +61,7 @@ func (c *client) getGRPCConn(ctx context.Context) (*grpc.ClientConn, error) {
),
)
conn, err := grpc.DialContext(ctx, c.config.GetConnectionURI(), opts...)
conn, err := grpc.NewClient(c.config.GetConnectionURI(), opts...)
if err != nil {
return nil, fmt.Errorf("error dialing grpc server: %w", err)
}
@ -92,7 +93,7 @@ func (c *client) logConnectionState(ctx context.Context, conn *grpc.ClientConn)
_ = conn.WaitForStateChange(ctx, state)
state = conn.GetState()
log.Ctx(ctx).Debug().
Str("endpoint", c.config.connectionURI).
Str("endpoint", c.config.GetConnectionURI()).
Str("state", state.String()).
Msg("grpc connection state")
}

View file

@ -17,7 +17,8 @@ import (
// config is the configuration for the gRPC client
type config struct {
connectionURI string
// authority is a host:port string that will be used as the :authority pseudo-header
authority string
// requireTLS is whether TLS should be used or cleartext
requireTLS bool
// opts are additional options to pass to the gRPC client
@ -41,9 +42,14 @@ func getConfig(
return c, nil
}
// GetAuthority returns the authority to use in the :authority pseudo-header
func (c *config) GetAuthority() string {
return c.authority
}
// GetConnectionURI returns connection string conforming to https://github.com/grpc/grpc/blob/master/doc/naming.md
func (c *config) GetConnectionURI() string {
return c.connectionURI
return "dns:" + c.authority
}
// GetDialTimeout returns the timeout for the dial operation
@ -101,7 +107,7 @@ func (c *config) parseEndpoint(endpoint string) error {
return fmt.Errorf("unsupported url scheme: %s", u.Scheme)
}
c.connectionURI = fmt.Sprintf("dns:%s:%s", host, port)
c.authority = host + ":" + port
c.requireTLS = requireTLS
return nil

View file

@ -23,11 +23,11 @@ import (
"github.com/pomerium/pomerium/internal/events"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/registry"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/version"
derivecert_config "github.com/pomerium/pomerium/pkg/derivecert/config"
"github.com/pomerium/pomerium/pkg/envoy"
"github.com/pomerium/pomerium/pkg/envoy/files"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/pomerium/pomerium/proxy"
oteltrace "go.opentelemetry.io/otel/trace"
)

View file

@ -713,7 +713,7 @@ func TestSharedResourceMonitor(t *testing.T) {
}
func TestBootstrapConfig(t *testing.T) {
b := envoyconfig.New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
b := envoyconfig.New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil, true)
testEnvoyPid := 99
tempDir := t.TempDir()
monitor, err := NewSharedResourceMonitor(context.Background(), config.NewStaticSource(nil), tempDir, WithCgroupDriver(&cgroupV2Driver{

File diff suppressed because it is too large Load diff

View file

@ -123,7 +123,7 @@ message Route {
string kubernetes_service_account_token = 26;
string kubernetes_service_account_token_file = 64;
bool enable_google_cloud_serverless_authentication = 42;
IssuerFormat jwt_issuer_format = 65;
optional IssuerFormat jwt_issuer_format = 65;
repeated string jwt_groups_filter = 66;
optional BearerTokenFormat bearer_token_format = 70;
@ -160,7 +160,7 @@ message Policy {
string remediation = 9;
}
// Next ID: 139.
// Next ID: 140.
message Settings {
message Certificate {
bytes cert_bytes = 3;
@ -214,6 +214,7 @@ message Settings {
map<string, string> set_response_headers = 69;
// repeated string jwt_claims_headers = 37;
map<string, string> jwt_claims_headers = 63;
optional IssuerFormat jwt_issuer_format = 139;
repeated string jwt_groups_filter = 119;
optional BearerTokenFormat bearer_token_format = 138;
optional google.protobuf.Duration default_upstream_timeout = 39;

View file

@ -16,11 +16,11 @@ import (
"golang.org/x/oauth2"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/identity/identity"
"github.com/pomerium/pomerium/pkg/identity/oauth"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// Name identifies the generic OpenID Connect provider.

View file

@ -4,7 +4,7 @@ import (
"net/url"
"testing"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
)

View file

@ -13,13 +13,13 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/testenv"
"github.com/pomerium/pomerium/internal/testenv/scenarios"
"github.com/pomerium/pomerium/internal/testenv/snippets"
. "github.com/pomerium/pomerium/internal/testutil/tracetest" //nolint:revive
"github.com/pomerium/pomerium/internal/testutil/tracetest/mock_otlptrace"
"github.com/pomerium/pomerium/internal/version"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"

View file

@ -9,8 +9,8 @@ import (
"testing"
"time"
"github.com/pomerium/pomerium/internal/telemetry/trace"
. "github.com/pomerium/pomerium/internal/testutil/tracetest" //nolint:revive
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
oteltrace "go.opentelemetry.io/otel/trace"

View file

@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace/noop"

View file

@ -4,7 +4,7 @@ import (
"os"
"testing"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
func TestMain(m *testing.M) {

View file

@ -9,7 +9,7 @@ import (
"time"
"github.com/gorilla/mux"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"

View file

@ -17,7 +17,6 @@ import (
"github.com/pomerium/datasource/pkg/directory"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/databroker"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/testutil"
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
@ -47,7 +46,7 @@ func Test_getUserInfoData(t *testing.T) {
proxy.state.Load().dataBrokerClient = client
r := httptest.NewRequest(http.MethodGet, "/.pomerium/", nil)
r.Header.Set(httputil.HeaderPomeriumIDPAccessToken, "ACCESS_TOKEN")
r.Header.Set("Authorization", "Bearer ACCESS_TOKEN")
data := proxy.getUserInfoData(r)
assert.NotNil(t, data.Session)
assert.NotNil(t, data.User)

View file

@ -15,8 +15,8 @@ import (
"github.com/pomerium/pomerium/internal/handlers"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/middleware"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
)
// registerDashboardHandlers returns the proxy service's ServeMux

View file

@ -19,8 +19,8 @@ import (
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/metrics"
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/telemetry/trace"
"github.com/pomerium/pomerium/proxy/portal"
)

View file

@ -30,6 +30,8 @@ func testOptions(t *testing.T) *config.Options {
opts.Services = config.ServiceAll
opts.SharedKey = "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ="
opts.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw="
bearerTokenFormatIDPAccessToken := config.BearerTokenFormatIDPAccessToken
opts.BearerTokenFormat = &bearerTokenFormatIDPAccessToken
hpkePrivateKey, err := opts.GetHPKEPrivateKey()
require.NoError(t, err)