mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-08 13:52:53 +02:00
authorize: add support for passing access or id token upstream (#3047)
* authorize: add support for passing access or id token upstream * use an enum
This commit is contained in:
parent
7140562a82
commit
99b9a3ee12
9 changed files with 726 additions and 538 deletions
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/pomerium/pomerium/config"
|
"github.com/pomerium/pomerium/config"
|
||||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||||
"github.com/pomerium/pomerium/internal/urlutil"
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
|
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeadersRequest is the input to the headers.rego script.
|
// HeadersRequest is the input to the headers.rego script.
|
||||||
|
@ -22,6 +23,8 @@ type HeadersRequest struct {
|
||||||
KubernetesServiceAccountToken string `json:"kubernetes_service_account_token"`
|
KubernetesServiceAccountToken string `json:"kubernetes_service_account_token"`
|
||||||
ToAudience string `json:"to_audience"`
|
ToAudience string `json:"to_audience"`
|
||||||
Session RequestSession `json:"session"`
|
Session RequestSession `json:"session"`
|
||||||
|
PassAccessToken bool `json:"pass_access_token"`
|
||||||
|
PassIDToken bool `json:"pass_id_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
|
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
|
||||||
|
@ -37,6 +40,8 @@ func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest {
|
||||||
for _, wu := range policy.To {
|
for _, wu := range policy.To {
|
||||||
input.ToAudience = "https://" + wu.URL.Hostname()
|
input.ToAudience = "https://" + wu.URL.Hostname()
|
||||||
}
|
}
|
||||||
|
input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN
|
||||||
|
input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,4 +106,40 @@ func TestHeadersEvaluator(t *testing.T) {
|
||||||
assert.Equal(t, "u2", claims["sub"], "should set subject to user id")
|
assert.Equal(t, "u2", claims["sub"], "should set subject to user id")
|
||||||
assert.Equal(t, "u2", claims["user"], "should set user to user id")
|
assert.Equal(t, "u2", claims["user"], "should set user to user id")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("access token", func(t *testing.T) {
|
||||||
|
output, err := eval(t,
|
||||||
|
[]proto.Message{
|
||||||
|
&session.Session{Id: "s1", OauthToken: &session.OAuthToken{
|
||||||
|
AccessToken: "ACCESS_TOKEN",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
&HeadersRequest{
|
||||||
|
FromAudience: "from.example.com",
|
||||||
|
ToAudience: "to.example.com",
|
||||||
|
Session: RequestSession{ID: "s1"},
|
||||||
|
PassAccessToken: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "Bearer ACCESS_TOKEN", output.Headers.Get("Authorization"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("id token", func(t *testing.T) {
|
||||||
|
output, err := eval(t,
|
||||||
|
[]proto.Message{
|
||||||
|
&session.Session{Id: "s1", IdToken: &session.IDToken{
|
||||||
|
Raw: "ID_TOKEN",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
&HeadersRequest{
|
||||||
|
FromAudience: "from.example.com",
|
||||||
|
ToAudience: "to.example.com",
|
||||||
|
Session: RequestSession{ID: "s1"},
|
||||||
|
PassIDToken: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "Bearer ID_TOKEN", output.Headers.Get("Authorization"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ package pomerium.headers
|
||||||
# session:
|
# session:
|
||||||
# id: string
|
# id: string
|
||||||
# to_audience: string
|
# to_audience: string
|
||||||
|
# pass_access_token: boolean
|
||||||
|
# pass_id_token: boolean
|
||||||
#
|
#
|
||||||
# data:
|
# data:
|
||||||
# issuer: string
|
# issuer: string
|
||||||
|
@ -153,7 +155,7 @@ base_jwt_claims := [
|
||||||
["user", jwt_payload_user],
|
["user", jwt_payload_user],
|
||||||
["email", jwt_payload_email],
|
["email", jwt_payload_email],
|
||||||
["groups", jwt_payload_groups],
|
["groups", jwt_payload_groups],
|
||||||
["sid", jwt_payload_sid]
|
["sid", jwt_payload_sid],
|
||||||
]
|
]
|
||||||
|
|
||||||
additional_jwt_claims := [[k, v] |
|
additional_jwt_claims := [[k, v] |
|
||||||
|
@ -209,9 +211,21 @@ google_cloud_serverless_headers = h {
|
||||||
|
|
||||||
routing_key_headers = h {
|
routing_key_headers = h {
|
||||||
input.enable_routing_key
|
input.enable_routing_key
|
||||||
h := [
|
h := [["x-pomerium-routing-key", crypto.sha256(input.session.id)]]
|
||||||
["x-pomerium-routing-key", crypto.sha256(input.session.id)]
|
} else = [] {
|
||||||
]
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pass_access_token_headers = h {
|
||||||
|
input.pass_access_token
|
||||||
|
h := [["Authorization", concat(" ", ["Bearer", session.oauth_token.access_token])]]
|
||||||
|
} else = [] {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pass_id_token_headers = h {
|
||||||
|
input.pass_id_token
|
||||||
|
h := [["Authorization", concat(" ", ["Bearer", session.id_token.raw])]]
|
||||||
} else = [] {
|
} else = [] {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -226,16 +240,19 @@ identity_headers := {key: values |
|
||||||
[ck, cv] := jwt_claims[_]
|
[ck, cv] := jwt_claims[_]
|
||||||
ck == k
|
ck == k
|
||||||
],
|
],
|
||||||
[""]
|
[""],
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
header_value := get_header_string_value(raw_header_value)
|
header_value := get_header_string_value(raw_header_value)
|
||||||
]
|
]
|
||||||
|
|
||||||
h3 := kubernetes_headers
|
h3 := kubernetes_headers
|
||||||
h4 := [[k, v] | v := google_cloud_serverless_headers[k]]
|
h4 := [[k, v] | v := google_cloud_serverless_headers[k]]
|
||||||
h5 := routing_key_headers
|
h5 := routing_key_headers
|
||||||
|
h6 := pass_access_token_headers
|
||||||
|
h7 := pass_id_token_headers
|
||||||
|
|
||||||
h := array.concat(array.concat(array.concat(array.concat(h1, h2), h3), h4), h5)
|
h := array.concat(array.concat(array.concat(array.concat(array.concat(array.concat(h1, h2), h3), h4), h5), h6), h7)
|
||||||
|
|
||||||
some i
|
some i
|
||||||
[key, v1] := h[i]
|
[key, v1] := h[i]
|
||||||
|
|
|
@ -117,6 +117,10 @@ type Policy struct {
|
||||||
TLSDownstreamClientCA string `mapstructure:"tls_downstream_client_ca" yaml:"tls_downstream_client_ca,omitempty"`
|
TLSDownstreamClientCA string `mapstructure:"tls_downstream_client_ca" yaml:"tls_downstream_client_ca,omitempty"`
|
||||||
TLSDownstreamClientCAFile string `mapstructure:"tls_downstream_client_ca_file" yaml:"tls_downstream_client_ca_file,omitempty"`
|
TLSDownstreamClientCAFile string `mapstructure:"tls_downstream_client_ca_file" yaml:"tls_downstream_client_ca_file,omitempty"`
|
||||||
|
|
||||||
|
// SetAuthorizationHeader sets the authorization request header based on the user's identity. Supported modes are
|
||||||
|
// `pass_through`, `access_token` and `id_token`.
|
||||||
|
SetAuthorizationHeader string `mapstructure:"set_authorization_header" yaml:"set_authorization_header,omitempty"`
|
||||||
|
|
||||||
// SetRequestHeaders adds a collection of headers to the upstream request
|
// SetRequestHeaders adds a collection of headers to the upstream request
|
||||||
// in the form of key value pairs. Note bene, this will overwrite the
|
// in the form of key value pairs. Note bene, this will overwrite the
|
||||||
// value of any existing value of a given header key.
|
// value of any existing value of a given header key.
|
||||||
|
@ -242,6 +246,7 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
|
||||||
TLSClientKeyFile: pb.GetTlsClientKeyFile(),
|
TLSClientKeyFile: pb.GetTlsClientKeyFile(),
|
||||||
TLSDownstreamClientCA: pb.GetTlsDownstreamClientCa(),
|
TLSDownstreamClientCA: pb.GetTlsDownstreamClientCa(),
|
||||||
TLSDownstreamClientCAFile: pb.GetTlsDownstreamClientCaFile(),
|
TLSDownstreamClientCAFile: pb.GetTlsDownstreamClientCaFile(),
|
||||||
|
SetAuthorizationHeader: pb.GetSetAuthorizationHeader().String(),
|
||||||
SetRequestHeaders: pb.GetSetRequestHeaders(),
|
SetRequestHeaders: pb.GetSetRequestHeaders(),
|
||||||
RemoveRequestHeaders: pb.GetRemoveRequestHeaders(),
|
RemoveRequestHeaders: pb.GetRemoveRequestHeaders(),
|
||||||
PreserveHostHeader: pb.GetPreserveHostHeader(),
|
PreserveHostHeader: pb.GetPreserveHostHeader(),
|
||||||
|
@ -364,6 +369,7 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
|
||||||
RemoveRequestHeaders: p.RemoveRequestHeaders,
|
RemoveRequestHeaders: p.RemoveRequestHeaders,
|
||||||
PreserveHostHeader: p.PreserveHostHeader,
|
PreserveHostHeader: p.PreserveHostHeader,
|
||||||
PassIdentityHeaders: p.PassIdentityHeaders,
|
PassIdentityHeaders: p.PassIdentityHeaders,
|
||||||
|
SetAuthorizationHeader: p.GetSetAuthorizationHeader(),
|
||||||
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
|
KubernetesServiceAccountToken: p.KubernetesServiceAccountToken,
|
||||||
Policies: sps,
|
Policies: sps,
|
||||||
SetResponseHeaders: p.SetResponseHeaders,
|
SetResponseHeaders: p.SetResponseHeaders,
|
||||||
|
@ -513,6 +519,10 @@ func (p *Policy) Validate() error {
|
||||||
p.compiledRegex, _ = regexp.Compile(rawRE)
|
p.compiledRegex, _ = regexp.Compile(rawRE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := configpb.Route_AuthorizationHeaderModeFromString(p.SetAuthorizationHeader); !ok && p.SetAuthorizationHeader != "" {
|
||||||
|
return fmt.Errorf("config: invalid policy set_authorization_header: %v", p.SetAuthorizationHeader)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,6 +649,12 @@ func (p *Policy) AllAllowedUsers() []string {
|
||||||
return aus
|
return aus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSetAuthorizationHeader gets the set authorization header mode.
|
||||||
|
func (p *Policy) GetSetAuthorizationHeader() configpb.Route_AuthorizationHeaderMode {
|
||||||
|
mode, _ := configpb.Route_AuthorizationHeaderModeFromString(p.SetAuthorizationHeader)
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
// StringURL stores a URL as a string in json.
|
// StringURL stores a URL as a string in json.
|
||||||
type StringURL struct {
|
type StringURL struct {
|
||||||
*url.URL
|
*url.URL
|
||||||
|
|
|
@ -1476,6 +1476,18 @@ explicitly set, then `timeout` would be unlimited (`0s`). You still may specify
|
||||||
of the connection using `timeout` value (i.e. to 1 day).
|
of the connection using `timeout` value (i.e. to 1 day).
|
||||||
|
|
||||||
|
|
||||||
|
### Set Authorization Header
|
||||||
|
- `yaml`/`json` setting: `set_authorization_header`
|
||||||
|
- Type: `string` (`pass_through`, `access_token` or `id_token`)
|
||||||
|
- Optional
|
||||||
|
- Default: `pass_through`
|
||||||
|
|
||||||
|
Set Authorization Header allows you to send a user's identity token through as a Bearer token in the Authorization header.
|
||||||
|
|
||||||
|
Use `access_token` to send the OAuth access token, `id_token` to send the OIDC id token, or `pass_through` (the default) to leave the Authorization header unchanged
|
||||||
|
when it's not used for Pomerium authentication.
|
||||||
|
|
||||||
|
|
||||||
### Set Request Headers
|
### Set Request Headers
|
||||||
- Config File Key: `set_request_headers`
|
- Config File Key: `set_request_headers`
|
||||||
- Type: map of `strings` key value pairs
|
- Type: map of `strings` key value pairs
|
||||||
|
|
|
@ -1622,6 +1622,19 @@ settings:
|
||||||
or set it to unlimited (`0s`). If `idle_timeout` is specified, and `timeout` is not
|
or set it to unlimited (`0s`). If `idle_timeout` is specified, and `timeout` is not
|
||||||
explicitly set, then `timeout` would be unlimited (`0s`). You still may specify maximum lifetime
|
explicitly set, then `timeout` would be unlimited (`0s`). You still may specify maximum lifetime
|
||||||
of the connection using `timeout` value (i.e. to 1 day).
|
of the connection using `timeout` value (i.e. to 1 day).
|
||||||
|
|
||||||
|
- name: "Set Authorization Header"
|
||||||
|
keys: ["set_authorization_header"]
|
||||||
|
attributes: |
|
||||||
|
- `yaml`/`json` setting: `set_authorization_header`
|
||||||
|
- Type: `string` (`pass_through`, `access_token` or `id_token`)
|
||||||
|
- Optional
|
||||||
|
- Default: `pass_through`
|
||||||
|
doc: |
|
||||||
|
Set Authorization Header allows you to send a user's identity token through as a Bearer token in the Authorization header.
|
||||||
|
|
||||||
|
Use `access_token` to send the OAuth access token, `id_token` to send the OIDC id token, or `pass_through` (the default) to leave the Authorization header unchanged
|
||||||
|
when it's not used for Pomerium authentication.
|
||||||
- name: "Set Request Headers"
|
- name: "Set Request Headers"
|
||||||
keys: ["set_request_headers"]
|
keys: ["set_request_headers"]
|
||||||
attributes: |
|
attributes: |
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Package config contains protobuf definitions for config.
|
// Package config contains protobuf definitions for config.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// IsSet returns true if one of the route redirect options has been chosen.
|
// IsSet returns true if one of the route redirect options has been chosen.
|
||||||
func (rr *RouteRedirect) IsSet() bool {
|
func (rr *RouteRedirect) IsSet() bool {
|
||||||
if rr == nil {
|
if rr == nil {
|
||||||
|
@ -16,3 +18,9 @@ func (rr *RouteRedirect) IsSet() bool {
|
||||||
rr.SchemeRedirect != nil ||
|
rr.SchemeRedirect != nil ||
|
||||||
rr.HttpsRedirect != nil
|
rr.HttpsRedirect != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route_AuthorizationHeaderModeFromString returns the Route_AuthorizationHeaderMode from a string.
|
||||||
|
func Route_AuthorizationHeaderModeFromString(raw string) (Route_AuthorizationHeaderMode, bool) { //nolint
|
||||||
|
mode, ok := Route_AuthorizationHeaderMode_value[strings.ToUpper(raw)]
|
||||||
|
return Route_AuthorizationHeaderMode(mode), ok
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,6 +34,12 @@ message RouteRedirect {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Route {
|
message Route {
|
||||||
|
enum AuthorizationHeaderMode {
|
||||||
|
PASS_THROUGH = 0;
|
||||||
|
ACCESS_TOKEN = 1;
|
||||||
|
ID_TOKEN = 2;
|
||||||
|
}
|
||||||
|
|
||||||
string name = 1;
|
string name = 1;
|
||||||
|
|
||||||
string from = 2;
|
string from = 2;
|
||||||
|
@ -86,6 +92,7 @@ message Route {
|
||||||
repeated string remove_request_headers = 23;
|
repeated string remove_request_headers = 23;
|
||||||
map<string, string> set_response_headers = 41;
|
map<string, string> set_response_headers = 41;
|
||||||
repeated RouteRewriteHeader rewrite_response_headers = 40;
|
repeated RouteRewriteHeader rewrite_response_headers = 40;
|
||||||
|
AuthorizationHeaderMode set_authorization_header = 54;
|
||||||
|
|
||||||
bool preserve_host_header = 24;
|
bool preserve_host_header = 24;
|
||||||
bool pass_identity_headers = 25;
|
bool pass_identity_headers = 25;
|
||||||
|
@ -102,6 +109,7 @@ message Route {
|
||||||
optional string host_rewrite_header = 51;
|
optional string host_rewrite_header = 51;
|
||||||
optional string host_path_regex_rewrite_pattern = 52;
|
optional string host_path_regex_rewrite_pattern = 52;
|
||||||
optional string host_path_regex_rewrite_substitution = 53;
|
optional string host_path_regex_rewrite_substitution = 53;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Policy {
|
message Policy {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue