mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 02:09:15 +02:00
wip
This commit is contained in:
parent
f19dd8b371
commit
b138b2ea12
6 changed files with 1104 additions and 773 deletions
91
config/bearer_token_format.go
Normal file
91
config/bearer_token_format.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BearerTokenFormat specifies how bearer tokens are interepreted by Pomerium.
|
||||||
|
type BearerTokenFormat string
|
||||||
|
|
||||||
|
// Bearer Token Formats
|
||||||
|
const (
|
||||||
|
BearerTokenFormatDefault BearerTokenFormat = "default"
|
||||||
|
BearerTokenFormatIDPAccessToken BearerTokenFormat = "idp_access_token"
|
||||||
|
BearerTokenFormatIDPIdentityToken BearerTokenFormat = "idp_identity_token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBearerTokenFormat parses the BearerTokenFormat.
|
||||||
|
func ParseBearerTokenFormat(raw string) (BearerTokenFormat, error) {
|
||||||
|
switch BearerTokenFormat(strings.TrimSpace(strings.ToLower(raw))) {
|
||||||
|
case BearerTokenFormatDefault:
|
||||||
|
return BearerTokenFormatDefault, nil
|
||||||
|
case BearerTokenFormatIDPAccessToken:
|
||||||
|
return BearerTokenFormatIDPAccessToken, nil
|
||||||
|
case BearerTokenFormatIDPIdentityToken:
|
||||||
|
return BearerTokenFormatIDPIdentityToken, nil
|
||||||
|
}
|
||||||
|
return BearerTokenFormatDefault, fmt.Errorf("invalid bearer token format: %s", raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BearerTokenFormatFromPB(pbBearerTokenFormat *configpb.BearerTokenFormat) *BearerTokenFormat {
|
||||||
|
if pbBearerTokenFormat == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bearerTokenFormat := new(BearerTokenFormat)
|
||||||
|
*bearerTokenFormat = BearerTokenFormatDefault
|
||||||
|
|
||||||
|
switch *pbBearerTokenFormat {
|
||||||
|
case configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_DEFAULT:
|
||||||
|
*bearerTokenFormat = BearerTokenFormatDefault
|
||||||
|
case configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_IDP_ACCESS_TOKEN:
|
||||||
|
*bearerTokenFormat = BearerTokenFormatIDPAccessToken
|
||||||
|
case configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_IDP_IDENTITY_TOKEN:
|
||||||
|
*bearerTokenFormat = BearerTokenFormatIDPIdentityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return bearerTokenFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToEnvoy converts the bearer token format into a protobuf enum.
|
||||||
|
func (bearerTokenFormat *BearerTokenFormat) ToPB() *configpb.BearerTokenFormat {
|
||||||
|
if bearerTokenFormat == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch *bearerTokenFormat {
|
||||||
|
case BearerTokenFormatDefault:
|
||||||
|
return configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_DEFAULT.Enum()
|
||||||
|
case BearerTokenFormatIDPAccessToken:
|
||||||
|
return configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_IDP_ACCESS_TOKEN.Enum()
|
||||||
|
case BearerTokenFormatIDPIdentityToken:
|
||||||
|
return configpb.BearerTokenFormat_BEARER_TOKEN_FORMAT_IDP_IDENTITY_TOKEN.Enum()
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown bearer token format: %s", bearerTokenFormat))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBearerTokenFormatHookFunc() mapstructure.DecodeHookFunc {
|
||||||
|
return func(_, t reflect.Type, data any) (any, error) {
|
||||||
|
if t != reflect.TypeOf(BearerTokenFormat("")) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var raw string
|
||||||
|
err = json.Unmarshal(bs, &raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ParseBearerTokenFormat(raw)
|
||||||
|
}
|
||||||
|
}
|
|
@ -196,6 +196,13 @@ type Options struct {
|
||||||
// List of JWT claims to insert as x-pomerium-claim-* headers on proxied requests
|
// 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"`
|
JWTClaimsHeaders JWTClaimHeaders `mapstructure:"jwt_claims_headers" yaml:"jwt_claims_headers,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.
|
||||||
|
// - "idp_identity_token": The Bearer token will be interpreted as an IdP identity token.
|
||||||
|
// When unset "default" will be used.
|
||||||
|
BearerTokenFormat *BearerTokenFormat `mapstructure:"bearer_token_format" yaml:"bearer_token_format,omitempty"`
|
||||||
|
|
||||||
// Allowlist of group names/IDs to include in the Pomerium JWT.
|
// Allowlist of group names/IDs to include in the Pomerium JWT.
|
||||||
JWTGroupsFilter JWTGroupsFilter
|
JWTGroupsFilter JWTGroupsFilter
|
||||||
|
|
||||||
|
@ -1503,6 +1510,7 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi
|
||||||
set(&o.SigningKey, settings.SigningKey)
|
set(&o.SigningKey, settings.SigningKey)
|
||||||
setMap(&o.SetResponseHeaders, settings.SetResponseHeaders)
|
setMap(&o.SetResponseHeaders, settings.SetResponseHeaders)
|
||||||
setMap(&o.JWTClaimsHeaders, settings.JwtClaimsHeaders)
|
setMap(&o.JWTClaimsHeaders, settings.JwtClaimsHeaders)
|
||||||
|
o.BearerTokenFormat = BearerTokenFormatFromPB(settings.BearerTokenFormat)
|
||||||
if len(settings.JwtGroupsFilter) > 0 {
|
if len(settings.JwtGroupsFilter) > 0 {
|
||||||
o.JWTGroupsFilter = NewJWTGroupsFilter(settings.JwtGroupsFilter)
|
o.JWTGroupsFilter = NewJWTGroupsFilter(settings.JwtGroupsFilter)
|
||||||
}
|
}
|
||||||
|
@ -1614,6 +1622,7 @@ func (o *Options) ToProto() *config.Config {
|
||||||
copySrcToOptionalDest(&settings.SigningKey, valueOrFromFileBase64(o.SigningKey, o.SigningKeyFile))
|
copySrcToOptionalDest(&settings.SigningKey, valueOrFromFileBase64(o.SigningKey, o.SigningKeyFile))
|
||||||
settings.SetResponseHeaders = o.SetResponseHeaders
|
settings.SetResponseHeaders = o.SetResponseHeaders
|
||||||
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
|
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
|
||||||
|
settings.BearerTokenFormat = o.BearerTokenFormat.ToPB()
|
||||||
settings.JwtGroupsFilter = o.JWTGroupsFilter.ToSlice()
|
settings.JwtGroupsFilter = o.JWTGroupsFilter.ToSlice()
|
||||||
copyOptionalDuration(&settings.DefaultUpstreamTimeout, o.DefaultUpstreamTimeout)
|
copyOptionalDuration(&settings.DefaultUpstreamTimeout, o.DefaultUpstreamTimeout)
|
||||||
copySrcToOptionalDest(&settings.MetricsAddress, &o.MetricsAddr)
|
copySrcToOptionalDest(&settings.MetricsAddress, &o.MetricsAddr)
|
||||||
|
|
|
@ -168,6 +168,12 @@ type Policy struct {
|
||||||
// - "hostOnly" (default): Issuer strings will be the hostname of the route, with no scheme or trailing slash.
|
// - "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.
|
// - "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 string `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.
|
||||||
|
// - "idp_identity_token": The Bearer token will be interpreted as an IdP identity token.
|
||||||
|
// When unset the global option will be used.
|
||||||
|
BearerTokenFormat *BearerTokenFormat `mapstructure:"bearer_token_format" yaml:"bearer_token_format,omitempty"`
|
||||||
|
|
||||||
// Allowlist of group names/IDs to include in the Pomerium JWT.
|
// Allowlist of group names/IDs to include in the Pomerium JWT.
|
||||||
// This expands on any global allowlist set in the main Options.
|
// This expands on any global allowlist set in the main Options.
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/encoding"
|
"github.com/pomerium/pomerium/internal/encoding"
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
|
@ -11,6 +13,7 @@ import (
|
||||||
"github.com/pomerium/pomerium/internal/sessions/header"
|
"github.com/pomerium/pomerium/internal/sessions/header"
|
||||||
"github.com/pomerium/pomerium/internal/sessions/queryparam"
|
"github.com/pomerium/pomerium/internal/sessions/queryparam"
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
|
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A SessionStore saves and loads sessions based on the options.
|
// A SessionStore saves and loads sessions based on the options.
|
||||||
|
@ -112,3 +115,120 @@ func (store *SessionStore) LoadSessionStateAndCheckIDP(r *http.Request) (*sessio
|
||||||
func (store *SessionStore) SaveSession(w http.ResponseWriter, r *http.Request, v any) error {
|
func (store *SessionStore) SaveSession(w http.ResponseWriter, r *http.Request, v any) error {
|
||||||
return store.store.SaveSession(w, r, v)
|
return store.store.SaveSession(w, r, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An IDPTokenSessionHandler handles incoming idp access and identity tokens.
|
||||||
|
type IDPTokenSessionHandler struct {
|
||||||
|
options *Options
|
||||||
|
getSession func(ctx context.Context, id string) (*session.Session, error)
|
||||||
|
putSession func(ctx context.Context, s *session.Session) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDPTokenSessionHandler creates a new IDPTokenSessionHandler.
|
||||||
|
func NewIDPTokenSessionHandler(
|
||||||
|
options *Options,
|
||||||
|
getSession func(ctx context.Context, id string) (*session.Session, error),
|
||||||
|
putSession func(ctx context.Context, s *session.Session) error,
|
||||||
|
) *IDPTokenSessionHandler {
|
||||||
|
return &IDPTokenSessionHandler{
|
||||||
|
options: options,
|
||||||
|
getSession: getSession,
|
||||||
|
putSession: putSession,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // CreateSessionForIncomingIDPToken creates a session from an incoming idp access or identity token.
|
||||||
|
// // If no such tokens are found or they are invalid ErrNoSessionFound will be returned.
|
||||||
|
// func (h *IDPTokenSessionHandler) CreateSessionForIncomingIDPToken(r *http.Request) (*session.Session, error) {
|
||||||
|
// idp, err := h.options.GetIdentityProviderForRequestURL(urlutil.GetAbsoluteURL(r).String())
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil, sessions.ErrNoSessionFound
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (h *IDPTokenSessionHandler) getIncomingIDPAccessToken(r *http.Request) (rawAccessToken string, ok bool) {
|
||||||
|
// if h.options.
|
||||||
|
|
||||||
|
// return "", false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (h *IDPTokenSessionHandler) getIncomingIDPIdentityToken(r *http.Request) (rawIdentityToken string, ok bool) {
|
||||||
|
// return "", false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateSessionForIncomingIDPToken(
|
||||||
|
// r *http.Request,
|
||||||
|
// options *Options,
|
||||||
|
// policy *Policy,
|
||||||
|
// getSession func(ctx context.Context, id string) (*session.Session, error),
|
||||||
|
// putSession func(ctx context.Context, s *session.Session) error)(*session.Session, error) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// GetIncomingIDPAccessTokenForPolicy returns the raw idp access token from a request if there is one.
|
||||||
|
func (options *Options) GetIncomingIDPAccessTokenForPolicy(policy *Policy, r *http.Request) (rawAccessToken string, ok bool) {
|
||||||
|
bearerTokenFormat := BearerTokenFormatDefault
|
||||||
|
if options != nil && options.BearerTokenFormat != nil {
|
||||||
|
bearerTokenFormat = *options.BearerTokenFormat
|
||||||
|
}
|
||||||
|
if policy != nil && policy.BearerTokenFormat != nil {
|
||||||
|
bearerTokenFormat = *policy.BearerTokenFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if token := r.Header.Get("X-Pomerium-IDP-Access-Token"); token != "" {
|
||||||
|
return token, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth := r.Header.Get("Authorization"); auth != "" {
|
||||||
|
prefix := "Pomerium-IDP-Access-Token "
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = "Bearer Pomerium-IDP-Access-Token-"
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = "Bearer "
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) && bearerTokenFormat == BearerTokenFormatIDPAccessToken {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIncomingIDPAccessTokenForPolicy returns the raw idp identity token from a request if there is one.
|
||||||
|
func (options *Options) GetIncomingIDPIdentityTokenForPolicy(policy *Policy, r *http.Request) (rawIdentityToken string, ok bool) {
|
||||||
|
bearerTokenFormat := BearerTokenFormatDefault
|
||||||
|
if options != nil && options.BearerTokenFormat != nil {
|
||||||
|
bearerTokenFormat = *options.BearerTokenFormat
|
||||||
|
}
|
||||||
|
if policy != nil && policy.BearerTokenFormat != nil {
|
||||||
|
bearerTokenFormat = *policy.BearerTokenFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if token := r.Header.Get("X-Pomerium-IDP-Identity-Token"); token != "" {
|
||||||
|
return token, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth := r.Header.Get("Authorization"); auth != "" {
|
||||||
|
prefix := "Pomerium-IDP-Identity-Token "
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = "Bearer Pomerium-IDP-Identity-Token-"
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = "Bearer "
|
||||||
|
if strings.HasPrefix(strings.ToLower(auth), strings.ToLower(prefix)) && bearerTokenFormat == BearerTokenFormatIDPIdentityToken {
|
||||||
|
return strings.TrimPrefix(auth, prefix), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -45,7 +45,14 @@ enum IssuerFormat {
|
||||||
IssuerURI = 1;
|
IssuerURI = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next ID: 70.
|
enum BearerTokenFormat {
|
||||||
|
BEARER_TOKEN_FORMAT_UNKNOWN = 0;
|
||||||
|
BEARER_TOKEN_FORMAT_DEFAULT = 1;
|
||||||
|
BEARER_TOKEN_FORMAT_IDP_ACCESS_TOKEN = 2;
|
||||||
|
BEARER_TOKEN_FORMAT_IDP_IDENTITY_TOKEN = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next ID: 71.
|
||||||
message Route {
|
message Route {
|
||||||
message StringList { repeated string values = 1; }
|
message StringList { repeated string values = 1; }
|
||||||
|
|
||||||
|
@ -118,6 +125,7 @@ message Route {
|
||||||
bool enable_google_cloud_serverless_authentication = 42;
|
bool enable_google_cloud_serverless_authentication = 42;
|
||||||
IssuerFormat jwt_issuer_format = 65;
|
IssuerFormat jwt_issuer_format = 65;
|
||||||
repeated string jwt_groups_filter = 66;
|
repeated string jwt_groups_filter = 66;
|
||||||
|
optional BearerTokenFormat bearer_token_format = 70;
|
||||||
|
|
||||||
envoy.config.cluster.v3.Cluster envoy_opts = 36;
|
envoy.config.cluster.v3.Cluster envoy_opts = 36;
|
||||||
|
|
||||||
|
@ -152,7 +160,7 @@ message Policy {
|
||||||
string remediation = 9;
|
string remediation = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next ID: 138.
|
// Next ID: 139.
|
||||||
message Settings {
|
message Settings {
|
||||||
message Certificate {
|
message Certificate {
|
||||||
bytes cert_bytes = 3;
|
bytes cert_bytes = 3;
|
||||||
|
@ -207,6 +215,7 @@ message Settings {
|
||||||
// repeated string jwt_claims_headers = 37;
|
// repeated string jwt_claims_headers = 37;
|
||||||
map<string, string> jwt_claims_headers = 63;
|
map<string, string> jwt_claims_headers = 63;
|
||||||
repeated string jwt_groups_filter = 119;
|
repeated string jwt_groups_filter = 119;
|
||||||
|
optional BearerTokenFormat bearer_token_format = 138;
|
||||||
optional google.protobuf.Duration default_upstream_timeout = 39;
|
optional google.protobuf.Duration default_upstream_timeout = 39;
|
||||||
optional string metrics_address = 40;
|
optional string metrics_address = 40;
|
||||||
optional string metrics_basic_auth = 64;
|
optional string metrics_basic_auth = 64;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue