authorize: move sign out and jwks urls to route, update issuer for JWT (#4046)

* authorize: move sign out and jwks urls to route, update issuer for JWT

* fix test
This commit is contained in:
Caleb Doxsey 2023-03-08 12:40:15 -07:00 committed by GitHub
parent 376bfe053d
commit 1dee325b72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 36 additions and 34 deletions

View file

@ -17,7 +17,6 @@ import (
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/contextutil" "github.com/pomerium/pomerium/pkg/contextutil"
"github.com/pomerium/pomerium/pkg/cryptutil" "github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/policy/criteria" "github.com/pomerium/pomerium/pkg/policy/criteria"
@ -204,12 +203,6 @@ func (e *Evaluator) updateStore(cfg *evaluatorConfig) error {
return fmt.Errorf("authorize: couldn't create signer: %w", err) return fmt.Errorf("authorize: couldn't create signer: %w", err)
} }
authenticateURL, err := urlutil.ParseAndValidateURL(cfg.authenticateURL)
if err != nil {
return fmt.Errorf("authorize: invalid authenticate URL: %w", err)
}
e.store.UpdateIssuer(authenticateURL.Host)
e.store.UpdateGoogleCloudServerlessAuthenticationServiceAccount( e.store.UpdateGoogleCloudServerlessAuthenticationServiceAccount(
cfg.googleCloudServerlessAuthenticationServiceAccount, cfg.googleCloudServerlessAuthenticationServiceAccount,
) )

View file

@ -33,7 +33,6 @@ func TestEvaluator(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...)) ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...))
store := store.New() store := store.New()
store.UpdateIssuer("authenticate.example.com")
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY")) store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
store.UpdateSigningKey(privateJWK) store.UpdateSigningKey(privateJWK)
e, err := New(ctx, store, options...) e, err := New(ctx, store, options...)

View file

@ -20,7 +20,7 @@ import (
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"`
FromAudience string `json:"from_audience"` 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"`
@ -35,7 +35,7 @@ func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest {
input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH || input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
if u, err := urlutil.ParseAndValidateURL(policy.From); err == nil { if u, err := urlutil.ParseAndValidateURL(policy.From); err == nil {
input.FromAudience = u.Hostname() input.Issuer = u.Hostname()
} }
input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken
for _, wu := range policy.To { for _, wu := range policy.To {

View file

@ -31,8 +31,8 @@ func TestNewHeadersRequestFromPolicy(t *testing.T) {
}) })
assert.Equal(t, &HeadersRequest{ assert.Equal(t, &HeadersRequest{
EnableGoogleCloudServerlessAuthentication: true, EnableGoogleCloudServerlessAuthentication: true,
FromAudience: "from.example.com", Issuer: "from.example.com",
ToAudience: "https://to.example.com", ToAudience: "https://to.example.com",
}, req) }, req)
} }
@ -53,7 +53,6 @@ func TestHeadersEvaluator(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...)) ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...))
store := store.New() store := store.New()
store.UpdateIssuer("authenticate.example.com")
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY")) store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
store.UpdateSigningKey(privateJWK) store.UpdateSigningKey(privateJWK)
e, err := NewHeadersEvaluator(ctx, store) e, err := NewHeadersEvaluator(ctx, store)
@ -72,8 +71,8 @@ func TestHeadersEvaluator(t *testing.T) {
}}, }},
}, },
&HeadersRequest{ &HeadersRequest{
FromAudience: "from.example.com", Issuer: "from.example.com",
ToAudience: "to.example.com", ToAudience: "to.example.com",
Session: RequestSession{ Session: RequestSession{
ID: "s1", ID: "s1",
}, },
@ -87,6 +86,8 @@ func TestHeadersEvaluator(t *testing.T) {
err = rawJWT.Claims(publicJWK, &claims) err = rawJWT.Claims(publicJWK, &claims)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, claims["iss"], "from.example.com")
assert.Equal(t, claims["aud"], "from.example.com")
assert.Equal(t, claims["exp"], math.Round(claims["exp"].(float64))) assert.Equal(t, claims["exp"], math.Round(claims["exp"].(float64)))
assert.LessOrEqual(t, claims["exp"], float64(time.Now().Add(time.Minute*6).Unix()), assert.LessOrEqual(t, claims["exp"], float64(time.Now().Add(time.Minute*6).Unix()),
"JWT should expire within 5 minutes, but got: %v", claims["exp"]) "JWT should expire within 5 minutes, but got: %v", claims["exp"])
@ -104,7 +105,7 @@ func TestHeadersEvaluator(t *testing.T) {
}}, }},
}, },
&HeadersRequest{ &HeadersRequest{
FromAudience: "from.example.com", Issuer: "from.example.com",
ToAudience: "to.example.com", ToAudience: "to.example.com",
Session: RequestSession{ID: "s1"}, Session: RequestSession{ID: "s1"},
PassAccessToken: true, PassAccessToken: true,
@ -122,10 +123,10 @@ func TestHeadersEvaluator(t *testing.T) {
}}, }},
}, },
&HeadersRequest{ &HeadersRequest{
FromAudience: "from.example.com", Issuer: "from.example.com",
ToAudience: "to.example.com", ToAudience: "to.example.com",
Session: RequestSession{ID: "s1"}, Session: RequestSession{ID: "s1"},
PassIDToken: true, PassIDToken: true,
}) })
require.NoError(t, err) require.NoError(t, err)

View file

@ -3,7 +3,7 @@ package pomerium.headers
# input: # input:
# enable_google_cloud_serverless_authentication: boolean # enable_google_cloud_serverless_authentication: boolean
# enable_routing_key: boolean # enable_routing_key: boolean
# from_audience: string # issuer: string
# kubernetes_service_account_token: string # kubernetes_service_account_token: string
# session: # session:
# id: string # id: string
@ -12,7 +12,6 @@ package pomerium.headers
# pass_id_token: boolean # pass_id_token: boolean
# #
# data: # data:
# issuer: string
# jwt_claim_headers: map[string]string # jwt_claim_headers: map[string]string
# signing_key: # signing_key:
# alg: string # alg: string
@ -81,12 +80,16 @@ jwt_headers = {
} }
jwt_payload_aud = v { jwt_payload_aud = v {
v := input.from_audience v := input.issuer
} else = "" { } else = "" {
true true
} }
jwt_payload_iss = data.issuer jwt_payload_iss = v {
v := input.issuer
} else = "" {
true
}
jwt_payload_jti = v { jwt_payload_jti = v {
v = session.id v = session.id

View file

@ -35,7 +35,6 @@ func TestPolicyEvaluator(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...)) ctx = storage.WithQuerier(ctx, storage.NewStaticQuerier(data...))
store := store.New() store := store.New()
store.UpdateIssuer("authenticate.example.com")
store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY")) store.UpdateJWTClaimHeaders(config.NewJWTClaimHeaders("email", "groups", "user", "CUSTOM_KEY"))
store.UpdateSigningKey(privateJWK) store.UpdateSigningKey(privateJWK)
e, err := NewPolicyEvaluator(ctx, store, policy) e, err := NewPolicyEvaluator(ctx, store, policy)

View file

@ -36,11 +36,6 @@ func New() *Store {
} }
} }
// UpdateIssuer updates the issuer in the store. The issuer is used as part of JWT construction.
func (s *Store) UpdateIssuer(issuer string) {
s.write("/issuer", issuer)
}
// UpdateGoogleCloudServerlessAuthenticationServiceAccount updates the google cloud serverless authentication // UpdateGoogleCloudServerlessAuthenticationServiceAccount updates the google cloud serverless authentication
// service account in the store. // service account in the store.
func (s *Store) UpdateGoogleCloudServerlessAuthenticationServiceAccount(serviceAccount string) { func (s *Store) UpdateGoogleCloudServerlessAuthenticationServiceAccount(serviceAccount string) {

View file

@ -53,8 +53,8 @@ func TestServerHTTP(t *testing.T) {
expect := map[string]any{ expect := map[string]any{
"authentication_callback_endpoint": "https://authenticate.localhost.pomerium.io/oauth2/callback", "authentication_callback_endpoint": "https://authenticate.localhost.pomerium.io/oauth2/callback",
"frontchannel_logout_uri": "https://authenticate.localhost.pomerium.io/.pomerium/sign_out", "frontchannel_logout_uri": fmt.Sprintf("https://localhost:%s/.pomerium/sign_out", src.GetConfig().HTTPPort),
"jwks_uri": "https://authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json", "jwks_uri": fmt.Sprintf("https://localhost:%s/.well-known/pomerium/jwks.json", src.GetConfig().HTTPPort),
} }
assert.Equal(t, expect, actual) assert.Equal(t, expect, actual)
}) })

View file

@ -8,6 +8,7 @@ import (
"github.com/pomerium/csrf" "github.com/pomerium/csrf"
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/urlutil"
) )
// WellKnownPomerium returns the /.well-known/pomerium handler. // WellKnownPomerium returns the /.well-known/pomerium handler.
@ -19,8 +20,8 @@ func WellKnownPomerium(authenticateURL *url.URL) http.Handler {
FrontchannelLogoutURI string `json:"frontchannel_logout_uri"` // https://openid.net/specs/openid-connect-frontchannel-1_0.html FrontchannelLogoutURI string `json:"frontchannel_logout_uri"` // https://openid.net/specs/openid-connect-frontchannel-1_0.html
}{ }{
authenticateURL.ResolveReference(&url.URL{Path: "/oauth2/callback"}).String(), authenticateURL.ResolveReference(&url.URL{Path: "/oauth2/callback"}).String(),
authenticateURL.ResolveReference(&url.URL{Path: "/.well-known/pomerium/jwks.json"}).String(), urlutil.GetAbsoluteURL(r).ResolveReference(&url.URL{Path: "/.well-known/pomerium/jwks.json"}).String(),
authenticateURL.ResolveReference(&url.URL{Path: "/.pomerium/sign_out"}).String(), urlutil.GetAbsoluteURL(r).ResolveReference(&url.URL{Path: "/.pomerium/sign_out"}).String(),
} }
w.Header().Set("X-CSRF-Token", csrf.Token(r)) w.Header().Set("X-CSRF-Token", csrf.Token(r))
httputil.RenderJSON(w, http.StatusOK, wellKnownURLs) httputil.RenderJSON(w, http.StatusOK, wellKnownURLs)

View file

@ -21,4 +21,15 @@ func TestWellKnownPomeriumHandler(t *testing.T) {
WellKnownPomerium(authenticateURL).ServeHTTP(w, r) WellKnownPomerium(authenticateURL).ServeHTTP(w, r)
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode) assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
}) })
t.Run("links", func(t *testing.T) {
authenticateURL, _ := url.Parse("https://authenticate.example.com")
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "https://route.example.com", nil)
WellKnownPomerium(authenticateURL).ServeHTTP(w, r)
assert.JSONEq(t, `{
"authentication_callback_endpoint": "https://authenticate.example.com/oauth2/callback",
"frontchannel_logout_uri": "https://route.example.com/.pomerium/sign_out",
"jwks_uri": "https://route.example.com/.well-known/pomerium/jwks.json"
}`, w.Body.String())
})
} }