mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-30 23:09:23 +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
|
||||
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.
|
||||
JWTGroupsFilter JWTGroupsFilter
|
||||
|
||||
|
@ -1503,6 +1510,7 @@ 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)
|
||||
if len(settings.JwtGroupsFilter) > 0 {
|
||||
o.JWTGroupsFilter = NewJWTGroupsFilter(settings.JwtGroupsFilter)
|
||||
}
|
||||
|
@ -1614,6 +1622,7 @@ func (o *Options) ToProto() *config.Config {
|
|||
copySrcToOptionalDest(&settings.SigningKey, valueOrFromFileBase64(o.SigningKey, o.SigningKeyFile))
|
||||
settings.SetResponseHeaders = o.SetResponseHeaders
|
||||
settings.JwtClaimsHeaders = o.JWTClaimsHeaders
|
||||
settings.BearerTokenFormat = o.BearerTokenFormat.ToPB()
|
||||
settings.JwtGroupsFilter = o.JWTGroupsFilter.ToSlice()
|
||||
copyOptionalDuration(&settings.DefaultUpstreamTimeout, o.DefaultUpstreamTimeout)
|
||||
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.
|
||||
// - "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"`
|
||||
// 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.
|
||||
// This expands on any global allowlist set in the main Options.
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"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/queryparam"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 StringList { repeated string values = 1; }
|
||||
|
||||
|
@ -118,6 +125,7 @@ message Route {
|
|||
bool enable_google_cloud_serverless_authentication = 42;
|
||||
IssuerFormat jwt_issuer_format = 65;
|
||||
repeated string jwt_groups_filter = 66;
|
||||
optional BearerTokenFormat bearer_token_format = 70;
|
||||
|
||||
envoy.config.cluster.v3.Cluster envoy_opts = 36;
|
||||
|
||||
|
@ -152,7 +160,7 @@ message Policy {
|
|||
string remediation = 9;
|
||||
}
|
||||
|
||||
// Next ID: 138.
|
||||
// Next ID: 139.
|
||||
message Settings {
|
||||
message Certificate {
|
||||
bytes cert_bytes = 3;
|
||||
|
@ -207,6 +215,7 @@ message Settings {
|
|||
// repeated string jwt_claims_headers = 37;
|
||||
map<string, string> jwt_claims_headers = 63;
|
||||
repeated string jwt_groups_filter = 119;
|
||||
optional BearerTokenFormat bearer_token_format = 138;
|
||||
optional google.protobuf.Duration default_upstream_timeout = 39;
|
||||
optional string metrics_address = 40;
|
||||
optional string metrics_basic_auth = 64;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue