config: add support for $pomerium.id_token and $pomerium.access_token in set_request_headers (#4219)

* config: add support for $pomerium.id_token and $pomerium.access_token in set_request_headers

* lint

* Update authorize/evaluator/headers_evaluator_test.go

Co-authored-by: Denis Mishin <dmishin@pomerium.com>

* fix spelling

---------

Co-authored-by: Denis Mishin <dmishin@pomerium.com>
This commit is contained in:
Caleb Doxsey 2023-06-01 16:00:02 -06:00 committed by GitHub
parent eb1d6841a0
commit 5be322e2ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 77 deletions

View file

@ -17,14 +17,15 @@ import (
// HeadersRequest is the input to the headers.rego script. // HeadersRequest is the input to the headers.rego script.
type HeadersRequest struct { type HeadersRequest struct {
EnableGoogleCloudServerlessAuthentication bool `json:"enable_google_cloud_serverless_authentication"` EnableGoogleCloudServerlessAuthentication bool `json:"enable_google_cloud_serverless_authentication"`
EnableRoutingKey bool `json:"enable_routing_key"` EnableRoutingKey bool `json:"enable_routing_key"`
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
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"` PassAccessToken bool `json:"pass_access_token"`
PassIDToken bool `json:"pass_id_token"` PassIDToken bool `json:"pass_id_token"`
SetRequestHeaders map[string]string `json:"set_request_headers"`
} }
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy. // NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
@ -41,6 +42,7 @@ func NewHeadersRequestFromPolicy(policy *config.Policy, hostname string) *Header
} }
input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN
input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN
input.SetRequestHeaders = policy.SetRequestHeaders
} }
return input return input
} }

View file

@ -139,4 +139,52 @@ func TestHeadersEvaluator(t *testing.T) {
assert.Equal(t, "Bearer ID_TOKEN", output.Headers.Get("Authorization")) assert.Equal(t, "Bearer ID_TOKEN", output.Headers.Get("Authorization"))
}) })
t.Run("set_request_headers", func(t *testing.T) {
output, err := eval(t,
[]proto.Message{
&session.Session{Id: "s1", IdToken: &session.IDToken{
Raw: "ID_TOKEN",
}, OauthToken: &session.OAuthToken{
AccessToken: "ACCESS_TOKEN",
}},
},
&HeadersRequest{
Issuer: "from.example.com",
ToAudience: "to.example.com",
Session: RequestSession{ID: "s1"},
SetRequestHeaders: map[string]string{
"X-Custom-Header": "CUSTOM_VALUE",
"X-ID-Token": "$pomerium.id_token",
"X-Access-Token": "$pomerium.access_token",
},
})
require.NoError(t, err)
assert.Equal(t, "CUSTOM_VALUE", output.Headers.Get("X-Custom-Header"))
assert.Equal(t, "ID_TOKEN", output.Headers.Get("X-ID-Token"))
assert.Equal(t, "ACCESS_TOKEN", output.Headers.Get("X-Access-Token"))
})
t.Run("set_request_headers original behavior", func(t *testing.T) {
output, err := eval(t,
[]proto.Message{
&session.Session{Id: "s1", IdToken: &session.IDToken{
Raw: "ID_TOKEN",
}, OauthToken: &session.OAuthToken{
AccessToken: "ACCESS_TOKEN",
}},
},
&HeadersRequest{
Issuer: "from.example.com",
ToAudience: "to.example.com",
Session: RequestSession{ID: "s1"},
SetRequestHeaders: map[string]string{
"Authorization": "Bearer $pomerium.id_token",
},
})
require.NoError(t, err)
assert.Equal(t, "Bearer ID_TOKEN", output.Headers.Get("Authorization"))
})
} }

View file

@ -10,6 +10,7 @@ package pomerium.headers
# to_audience: string # to_audience: string
# pass_access_token: boolean # pass_access_token: boolean
# pass_id_token: boolean # pass_id_token: boolean
# set_request_headers: map[string]string
# #
# data: # data:
# jwt_claim_headers: map[string]string # jwt_claim_headers: map[string]string
@ -46,30 +47,22 @@ session = v {
v = get_databroker_record("type.googleapis.com/session.Session", input.session.id) v = get_databroker_record("type.googleapis.com/session.Session", input.session.id)
v != null v != null
object.get(v, "impersonate_session_id", "") == "" object.get(v, "impersonate_session_id", "") == ""
} else = {} { } else = {}
true
}
user = u { user = u {
u = get_databroker_record("type.googleapis.com/user.User", session.user_id) u = get_databroker_record("type.googleapis.com/user.User", session.user_id)
u != null u != null
} else = {} { } else = {}
true
}
directory_user = du { directory_user = du {
du = get_databroker_record("pomerium.io/DirectoryUser", session.user_id) du = get_databroker_record("pomerium.io/DirectoryUser", session.user_id)
du != null du != null
} else = {} { } else = {}
true
}
group_ids = gs { group_ids = gs {
gs = directory_user.group_ids gs = directory_user.group_ids
gs != null gs != null
} else = [] { } else = []
true
}
groups := array.concat(group_ids, array.concat(get_databroker_group_names(group_ids), get_databroker_group_emails(group_ids))) groups := array.concat(group_ids, array.concat(get_databroker_group_names(group_ids), get_databroker_group_emails(group_ids)))
@ -81,29 +74,21 @@ jwt_headers = {
jwt_payload_aud = v { jwt_payload_aud = v {
v := input.issuer v := input.issuer
} else = "" { } else = ""
true
}
jwt_payload_iss = v { jwt_payload_iss = v {
v := input.issuer v := input.issuer
} else = "" { } else = ""
true
}
jwt_payload_jti = v { jwt_payload_jti = v {
v = session.id v = session.id
} else = "" { } else = ""
true
}
jwt_payload_exp = v { jwt_payload_exp = v {
v = min([five_minutes, round(session.expires_at.seconds)]) v = min([five_minutes, round(session.expires_at.seconds)])
} else = v { } else = v {
v = five_minutes v = five_minutes
} else = null { } else = null
true
}
jwt_payload_iat = v { jwt_payload_iat = v {
# sessions store the issued_at on the id_token # sessions store the issued_at on the id_token
@ -111,29 +96,21 @@ jwt_payload_iat = v {
} else = v { } else = v {
# service accounts store the issued at directly # service accounts store the issued at directly
v = round(session.issued_at.seconds) v = round(session.issued_at.seconds)
} else = null { } else = null
true
}
jwt_payload_sub = v { jwt_payload_sub = v {
v = session.user_id v = session.user_id
} else = "" { } else = ""
true
}
jwt_payload_user = v { jwt_payload_user = v {
v = session.user_id v = session.user_id
} else = "" { } else = ""
true
}
jwt_payload_email = v { jwt_payload_email = v {
v = directory_user.email v = directory_user.email
} else = v { } else = v {
v = user.email v = user.email
} else = "" { } else = ""
true
}
jwt_payload_groups = v { jwt_payload_groups = v {
v = array.concat(group_ids, get_databroker_group_names(group_ids)) v = array.concat(group_ids, get_databroker_group_names(group_ids))
@ -141,17 +118,13 @@ jwt_payload_groups = v {
} else = v { } else = v {
v = session.claims.groups v = session.claims.groups
v != null v != null
} else = [] { } else = []
true
}
jwt_payload_name = v { jwt_payload_name = v {
v = get_header_string_value(session.claims.name) v = get_header_string_value(session.claims.name)
} else = v { } else = v {
v = get_header_string_value(user.claims.name) v = get_header_string_value(user.claims.name)
} else = "" { } else = ""
true
}
# the session id is always set to the input session id, even if impersonating # the session id is always set to the input session id, even if impersonating
jwt_payload_sid := input.session.id jwt_payload_sid := input.session.id
@ -204,43 +177,49 @@ kubernetes_headers = h {
["Impersonate-User", jwt_payload_email], ["Impersonate-User", jwt_payload_email],
["Impersonate-Group", get_header_string_value(jwt_payload_groups)], ["Impersonate-Group", get_header_string_value(jwt_payload_groups)],
] ]
} else = [] { } else = []
true
}
google_cloud_serverless_authentication_service_account = s { google_cloud_serverless_authentication_service_account = s {
s := data.google_cloud_serverless_authentication_service_account s := data.google_cloud_serverless_authentication_service_account
} else = "" { } else = ""
true
}
google_cloud_serverless_headers = h { google_cloud_serverless_headers = h {
input.enable_google_cloud_serverless_authentication input.enable_google_cloud_serverless_authentication
h := get_google_cloud_serverless_headers(google_cloud_serverless_authentication_service_account, input.to_audience) h := get_google_cloud_serverless_headers(google_cloud_serverless_authentication_service_account, input.to_audience)
} else = {} { } else = {}
true
}
routing_key_headers = h { routing_key_headers = h {
input.enable_routing_key input.enable_routing_key
h := [["x-pomerium-routing-key", crypto.sha256(input.session.id)]] h := [["x-pomerium-routing-key", crypto.sha256(input.session.id)]]
} else = [] { } else = []
true
}
pass_access_token_headers = h { pass_access_token_headers = h {
input.pass_access_token input.pass_access_token
h := [["Authorization", concat(" ", ["Bearer", session.oauth_token.access_token])]] h := [["Authorization", concat(" ", ["Bearer", session.oauth_token.access_token])]]
} else = [] { } else = []
true
}
pass_id_token_headers = h { pass_id_token_headers = h {
input.pass_id_token input.pass_id_token
h := [["Authorization", concat(" ", ["Bearer", session.id_token.raw])]] h := [["Authorization", concat(" ", ["Bearer", session.id_token.raw])]]
} else = [] { } else = []
true
} session_id_token = v {
v := session.id_token.raw
} else = ""
session_access_token = v {
v := session.oauth_token.access_token
} else = ""
set_request_headers = h {
h := [[header_name, header_value] |
some header_name
v1 := input.set_request_headers[header_name]
v2 := replace(v1, "$pomerium.id_token", session_id_token)
v3 := replace(v2, "$pomerium.access_token", session_access_token)
header_value := v3
]
} else = []
identity_headers := {key: values | identity_headers := {key: values |
h1 := [["x-pomerium-jwt-assertion", signed_jwt]] h1 := [["x-pomerium-jwt-assertion", signed_jwt]]
@ -263,8 +242,9 @@ identity_headers := {key: values |
h5 := routing_key_headers h5 := routing_key_headers
h6 := pass_access_token_headers h6 := pass_access_token_headers
h7 := pass_id_token_headers h7 := pass_id_token_headers
h8 := set_request_headers
h := array.concat(array.concat(array.concat(array.concat(array.concat(array.concat(h1, h2), h3), h4), h5), h6), h7) h := array.concat(array.concat(array.concat(array.concat(array.concat(array.concat(array.concat(h1, h2), h3), h4), h5), h6), h7), h8)
some i some i
[key, v1] := h[i] [key, v1] := h[i]

View file

@ -277,7 +277,6 @@ func (b *Builder) buildRouteForPolicyAndMatch(
Name: name, Name: name,
Match: match, Match: match,
Metadata: &envoy_config_core_v3.Metadata{}, Metadata: &envoy_config_core_v3.Metadata{},
RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders),
RequestHeadersToRemove: getRequestHeadersToRemove(cfg.Options, policy), RequestHeadersToRemove: getRequestHeadersToRemove(cfg.Options, policy),
ResponseHeadersToAdd: toEnvoyHeaders(cfg.Options.GetSetResponseHeadersForPolicy(policy)), ResponseHeadersToAdd: toEnvoyHeaders(cfg.Options.GetSetResponseHeadersForPolicy(policy)),
} }

View file

@ -609,13 +609,6 @@ func Test_buildPolicyRoutes(t *testing.T) {
{ "enabled": false, "upgradeType": "spdy/3.1"} { "enabled": false, "upgradeType": "spdy/3.1"}
] ]
}, },
"requestHeadersToAdd": [{
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": {
"key": "HEADER-KEY",
"value": "HEADER-VALUE"
}
}],
"requestHeadersToRemove": [ "requestHeadersToRemove": [
"x-pomerium-reproxy-policy", "x-pomerium-reproxy-policy",
"x-pomerium-reproxy-policy-hmac" "x-pomerium-reproxy-policy-hmac"

View file

@ -542,6 +542,11 @@ func (p *Policy) Validate() error {
return fmt.Errorf("config: invalid policy set_authorization_header: %v", p.SetAuthorizationHeader) return fmt.Errorf("config: invalid policy set_authorization_header: %v", p.SetAuthorizationHeader)
} }
if p.SetAuthorizationHeader != "" {
log.Warn(context.Background()).Msg("config: set_authorization_header is deprecated, " +
"use $pomerium.id_token or $pomerium.access_token in set_request_headers instead")
}
return nil return nil
} }