auth: oidc: disable pkce by default (#25600)

our goal of "enable by default, only for new auth methods"
proved to be unwieldy, so instead make it a simple bool,
disabled by default.
This commit is contained in:
Daniel Bennett
2025-04-07 13:36:09 -04:00
committed by GitHub
parent 6c39285538
commit 5c8e436de9
8 changed files with 18 additions and 71 deletions

View File

@@ -829,9 +829,8 @@ type ACLAuthMethodConfig struct {
// Optionally send a signed JWT ("private key jwt") as a client assertion // Optionally send a signed JWT ("private key jwt") as a client assertion
// to the OIDC provider // to the OIDC provider
OIDCClientAssertion *OIDCClientAssertion OIDCClientAssertion *OIDCClientAssertion
// Enable S256 PKCE challenge verification. If nil, the Nomad server sets // Enable S256 PKCE challenge verification.
// this to true when creating an auth method. I.e. it is enabled by default. OIDCEnablePKCE bool
OIDCEnablePKCE *bool
// Disable claims from the OIDC UserInfo endpoint // Disable claims from the OIDC UserInfo endpoint
OIDCDisableUserInfo bool OIDCDisableUserInfo bool
// List of OIDC scopes // List of OIDC scopes

View File

@@ -96,7 +96,7 @@ func formatAuthMethodConfig(config *api.ACLAuthMethodConfig) string {
} }
out = append(out, formatClientAssertion(config.OIDCClientAssertion)...) out = append(out, formatClientAssertion(config.OIDCClientAssertion)...)
out = append(out, out = append(out,
fmt.Sprintf("OIDC Enable PKCE|%t", config.OIDCEnablePKCE != nil && *config.OIDCEnablePKCE), fmt.Sprintf("OIDC Enable PKCE|%t", config.OIDCEnablePKCE),
fmt.Sprintf("OIDC Disable UserInfo|%t", config.OIDCDisableUserInfo), fmt.Sprintf("OIDC Disable UserInfo|%t", config.OIDCDisableUserInfo),
fmt.Sprintf("OIDC Scopes|%s", strings.Join(config.OIDCScopes, ",")), fmt.Sprintf("OIDC Scopes|%s", strings.Join(config.OIDCScopes, ",")),
fmt.Sprintf("Bound audiences|%s", strings.Join(config.BoundAudiences, ",")), fmt.Sprintf("Bound audiences|%s", strings.Join(config.BoundAudiences, ",")),

View File

@@ -11,7 +11,6 @@ import (
capOIDC "github.com/hashicorp/cap/oidc" capOIDC "github.com/hashicorp/cap/oidc"
"github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/shoenig/test/must" "github.com/shoenig/test/must"
) )
@@ -95,12 +94,10 @@ func TestACLOIDC_CompleteAuth(t *testing.T) {
MaxTokenTTL: 10 * time.Hour, MaxTokenTTL: 10 * time.Hour,
Default: true, Default: true,
Config: &api.ACLAuthMethodConfig{ Config: &api.ACLAuthMethodConfig{
OIDCDiscoveryURL: oidcTestProvider.Addr(), OIDCDiscoveryURL: oidcTestProvider.Addr(),
OIDCClientID: "mock", OIDCClientID: "mock",
OIDCClientSecret: "verysecretsecret", OIDCClientSecret: "verysecretsecret",
// PKCE is hard to test at this level, because the verifier only OIDCEnablePKCE: false,
// exists on the server. this functionality is covered elsewhere.
OIDCEnablePKCE: pointer.Of(false),
OIDCDisableUserInfo: false, OIDCDisableUserInfo: false,
BoundAudiences: []string{"mock"}, BoundAudiences: []string{"mock"},
AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"}, AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"},

View File

@@ -20,8 +20,6 @@ import (
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
metrics "github.com/hashicorp/go-metrics/compat" metrics "github.com/hashicorp/go-metrics/compat"
"github.com/hashicorp/go-set/v3" "github.com/hashicorp/go-set/v3"
"github.com/hashicorp/nomad/helper/pointer"
policy "github.com/hashicorp/nomad/acl" policy "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/helper/uuid"
@@ -1912,12 +1910,6 @@ func (a *ACL) UpsertAuthMethods(
} }
} }
// if this is a new auth method, and PKCE is not explicitly disabled
// (by setting Enable=false), then enable it. existing auth methods
// need it to be enabled explicitly (Enable=True).
if existingMethod == nil && authMethod.Config.OIDCEnablePKCE == nil {
authMethod.Config.OIDCEnablePKCE = pointer.Of(true)
}
// if there is a client assertion, ensure it is valid. // if there is a client assertion, ensure it is valid.
if authMethod.Config.OIDCClientAssertion.IsSet() { if authMethod.Config.OIDCClientAssertion.IsSet() {
_, err := a.oidcClientAssertion(authMethod.Config) _, err := a.oidcClientAssertion(authMethod.Config)
@@ -3071,7 +3063,7 @@ func (a *ACL) oidcRequest(nonce, redirect string, config *structs.ACLAuthMethodC
opts = append(opts, capOIDC.WithAudiences(config.BoundAudiences...)) opts = append(opts, capOIDC.WithAudiences(config.BoundAudiences...))
} }
if config.OIDCEnablePKCE != nil && *config.OIDCEnablePKCE { if config.OIDCEnablePKCE {
verifier, err := capOIDC.NewCodeVerifier() verifier, err := capOIDC.NewCodeVerifier()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make pkce verifier: %w", err) return nil, fmt.Errorf("failed to make pkce verifier: %w", err)

View File

@@ -20,7 +20,6 @@ import (
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc/v2" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc/v2"
"github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/lib/auth/oidc" "github.com/hashicorp/nomad/lib/auth/oidc"
"github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/mock"
@@ -3141,38 +3140,6 @@ func TestACLEndpoint_UpsertACLAuthMethods(t *testing.T) {
} }
must.NoError(t, msgpackrpc.CallWithCodec(codec, structs.ACLUpsertAuthMethodsRPCMethod, req, &resp)) must.NoError(t, msgpackrpc.CallWithCodec(codec, structs.ACLUpsertAuthMethodsRPCMethod, req, &resp))
must.Eq(t, resp.AuthMethods[0].TokenLocality, am3.TokenLocality) must.Eq(t, resp.AuthMethods[0].TokenLocality, am3.TokenLocality)
// default PKCE behavior
// * for new auth methods, it should default to true
// * for existing auth methods, it should remain nil
t.Run("pkce", func(t *testing.T) {
amPKCE := mock.ACLOIDCAuthMethod()
// new auth method, should default to true
amPKCE.Config.OIDCEnablePKCE = nil
req = &structs.ACLAuthMethodUpsertRequest{
AuthMethods: []*structs.ACLAuthMethod{amPKCE},
WriteRequest: structs.WriteRequest{
Region: "global",
AuthToken: root.SecretID,
},
}
must.NoError(t, msgpackrpc.CallWithCodec(codec, structs.ACLUpsertAuthMethodsRPCMethod, req, &resp))
out, err = s1.fsm.State().GetACLAuthMethodByName(nil, amPKCE.Name)
must.NoError(t, err)
must.NotNil(t, out)
must.NotNil(t, out.Config)
must.True(t, *out.Config.OIDCEnablePKCE, must.Sprint("pkce should be enabled on new auth methods"))
// but should remain disabled on existing auth methods
// upsert it directly to state to set it back to nil (not possible in rpc upsert)
must.NoError(t, s1.fsm.State().UpsertACLAuthMethods(resp.Index+1, []*structs.ACLAuthMethod{amPKCE}))
must.NoError(t, msgpackrpc.CallWithCodec(codec, structs.ACLUpsertAuthMethodsRPCMethod, req, &resp))
out, err = s1.fsm.State().GetACLAuthMethodByName(nil, amPKCE.Name)
must.NoError(t, err)
must.NotNil(t, out)
must.Nil(t, out.Config.OIDCEnablePKCE, must.Sprint("pkce should remain disabled on existing auth methods"))
})
} }
func TestACL_UpsertBindingRules(t *testing.T) { func TestACL_UpsertBindingRules(t *testing.T) {
@@ -3668,7 +3635,7 @@ func TestACL_OIDCAuthURL(t *testing.T) {
t.Run("pkce", func(t *testing.T) { t.Run("pkce", func(t *testing.T) {
authMethod := mockedAuthMethod.Copy() authMethod := mockedAuthMethod.Copy()
authMethod.Name = mockedAuthMethod.Name + "-pkce" authMethod.Name = mockedAuthMethod.Name + "-pkce"
authMethod.Config.OIDCEnablePKCE = pointer.Of(true) authMethod.Config.OIDCEnablePKCE = true
authMethod.SetHash() authMethod.SetHash()
must.NoError(t, testServer.fsm.State().UpsertACLAuthMethods(20, []*structs.ACLAuthMethod{authMethod})) must.NoError(t, testServer.fsm.State().UpsertACLAuthMethods(20, []*structs.ACLAuthMethod{authMethod}))
@@ -3955,7 +3922,7 @@ func TestACL_OIDCCompleteAuth(t *testing.T) {
t.Run("pkce", func(t *testing.T) { t.Run("pkce", func(t *testing.T) {
mockedAuthMethod.Config.OIDCEnablePKCE = pointer.Of(true) mockedAuthMethod.Config.OIDCEnablePKCE = true
must.NoError(t, testServer.fsm.State().UpsertACLAuthMethods(60, []*structs.ACLAuthMethod{mockedAuthMethod})) must.NoError(t, testServer.fsm.State().UpsertACLAuthMethods(60, []*structs.ACLAuthMethod{mockedAuthMethod}))
req := structs.ACLOIDCCompleteAuthRequest{ req := structs.ACLOIDCCompleteAuthRequest{

View File

@@ -16,7 +16,6 @@ import (
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/hashicorp/nomad/helper/pointer"
testing "github.com/mitchellh/go-testing-interface" testing "github.com/mitchellh/go-testing-interface"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -273,12 +272,10 @@ func ACLOIDCAuthMethod() *structs.ACLAuthMethod {
MaxTokenTTL: maxTokenTTL, MaxTokenTTL: maxTokenTTL,
Default: false, Default: false,
Config: &structs.ACLAuthMethodConfig{ Config: &structs.ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com", OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock", OIDCClientID: "mock",
OIDCClientSecret: "very secret secret", OIDCClientSecret: "very secret secret",
// PKCE is hard to test outside the server/RPC layer, OIDCEnablePKCE: false,
// because the verifier is only accessible there.
OIDCEnablePKCE: pointer.Of(false),
OIDCDisableUserInfo: false, OIDCDisableUserInfo: false,
OIDCScopes: []string{"groups"}, OIDCScopes: []string{"groups"},
BoundAudiences: []string{"sales", "engineering"}, BoundAudiences: []string{"sales", "engineering"},

View File

@@ -796,9 +796,7 @@ func (a *ACLAuthMethod) SetHash() []byte {
_, _ = hash.Write([]byte(a.Config.OIDCDiscoveryURL)) _, _ = hash.Write([]byte(a.Config.OIDCDiscoveryURL))
_, _ = hash.Write([]byte(a.Config.OIDCClientID)) _, _ = hash.Write([]byte(a.Config.OIDCClientID))
_, _ = hash.Write([]byte(a.Config.OIDCClientSecret)) _, _ = hash.Write([]byte(a.Config.OIDCClientSecret))
if a.Config.OIDCEnablePKCE != nil { _, _ = hash.Write([]byte(strconv.FormatBool(a.Config.OIDCEnablePKCE)))
_, _ = hash.Write([]byte(strconv.FormatBool(*a.Config.OIDCEnablePKCE)))
}
_, _ = hash.Write([]byte(strconv.FormatBool(a.Config.OIDCDisableUserInfo))) _, _ = hash.Write([]byte(strconv.FormatBool(a.Config.OIDCDisableUserInfo)))
_, _ = hash.Write([]byte(strconv.FormatBool(a.Config.VerboseLogging))) _, _ = hash.Write([]byte(strconv.FormatBool(a.Config.VerboseLogging)))
_, _ = hash.Write([]byte(a.Config.ExpirationLeeway.String())) _, _ = hash.Write([]byte(a.Config.ExpirationLeeway.String()))
@@ -1062,9 +1060,7 @@ type ACLAuthMethodConfig struct {
OIDCClientAssertion *OIDCClientAssertion OIDCClientAssertion *OIDCClientAssertion
// Enable PKCE challenge verification // Enable PKCE challenge verification
// If nil, the ACL Upsert RPC endpoint sets it to &true, OIDCEnablePKCE bool
// if the auth method is brand new.
OIDCEnablePKCE *bool
// Disable claims from the OIDC UserInfo endpoint // Disable claims from the OIDC UserInfo endpoint
OIDCDisableUserInfo bool OIDCDisableUserInfo bool

View File

@@ -98,10 +98,9 @@ for the auth method.
alongside "kid" and "type". Setting the "kid" header here is not allowed; alongside "kid" and "type". Setting the "kid" header here is not allowed;
use `PrivateKey.KeyID`. use `PrivateKey.KeyID`.
- `OIDCEnablePKCE` `(bool: true)` - When set to `true`, Nomad will include - `OIDCEnablePKCE` `(bool: false)` - When set to `true`, Nomad will include
[PKCE][] verification in the auth flow. Even with PKCE enabled in Nomad, [PKCE][] verification in the auth flow. Even with PKCE enabled in Nomad,
which is the default setting, you may still need to enable it in your OIDC you may still need to enable it in your OIDC provider.
provider.
- `OIDCDisableUserInfo` `(bool: false)` - When set to `true`, Nomad will not - `OIDCDisableUserInfo` `(bool: false)` - When set to `true`, Nomad will not
make a request to the identity provider to get OIDC UserInfo. You may wish to make a request to the identity provider to get OIDC UserInfo. You may wish to