mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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, ",")),
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user