mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
oidc: more tests for client assertions (#25352)
Co-authored-by: dduzgun-security <deniz.duzgun@hashicorp.com>
This commit is contained in:
17
api/acl.go
17
api/acl.go
@@ -1023,14 +1023,14 @@ const (
|
||||
// PemCert, PemCertFile may contain an x509 certificate created with
|
||||
// the Key, used to derive the KeyID. Alternatively, KeyID may be set manually.
|
||||
//
|
||||
// PemKeyFile and PemCertFile, if set, must be present on disk on any Nomad
|
||||
// servers that may become cluster leaders.
|
||||
// PemKeyFile and PemCertFile, if set, must be an absolute path to a file
|
||||
// present on disk on any Nomad servers that may become cluster leaders.
|
||||
type OIDCClientAssertionKey struct {
|
||||
// PemKey is the private key, in pem format. It is used to sign the JWT.
|
||||
// Mutually exclusive with PemKeyFile.
|
||||
PemKey string
|
||||
// PemKeyFile is the path to a private key on server disk, in pem format.
|
||||
// It is used to sign the JWT.
|
||||
// PemKeyFile is an absolute path to a private key on Nomad servers' disk,
|
||||
// in pem format. It is used to sign the JWT.
|
||||
// Mutually exclusive with PemKey.
|
||||
PemKeyFile string
|
||||
|
||||
@@ -1057,13 +1057,14 @@ type OIDCClientAssertionKey struct {
|
||||
// Mutually exclusive with PemCert and PemCertFile.
|
||||
// Allowed KeyIDHeader values: "kid" (the default)
|
||||
KeyID string
|
||||
// PemCert is a certificate, signed by the private key or a CA,
|
||||
// in pem format. It is used to derive an x5t-style KeyID.
|
||||
// PemCert is an x509 certificate, signed by the private key or a CA,
|
||||
// in pem format. It is used to derive an x5t#S256 (or x5t) KeyID.
|
||||
// Mutually exclusive with PemCertFile and KeyID.
|
||||
// Allowed KeyIDHeader values: "x5t", "x5t#S256" (default "x5t#S256")
|
||||
PemCert string
|
||||
// PemCertFile is a certificate, signed by the private key or a CA,
|
||||
// on server disk, in pem format. It is used to derive an x5t-style KeyID.
|
||||
// PemCertFile is an absolute path to an x509 certificate on Nomad servers'
|
||||
// disk, signed by the private key or a CA, in pem format.
|
||||
// It is used to derive an x5t#S256 (or x5t) KeyID.
|
||||
// Mutually exclusive with PemCert and KeyID.
|
||||
// Allowed KeyIDHeader values: "x5t", "x5t#S256" (default "x5t#S256")
|
||||
PemCertFile string
|
||||
|
||||
@@ -153,7 +153,7 @@ func getCassCert(k *structs.OIDCClientAssertionKey) (*x509.Certificate, error) {
|
||||
}
|
||||
now := time.Now()
|
||||
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
|
||||
return nil, errors.New("cert expired or not yet valid")
|
||||
return nil, errors.New("certificate has expired or is not yet valid")
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
520
lib/auth/oidc/client_assertion_test.go
Normal file
520
lib/auth/oidc/client_assertion_test.go
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestBuildClientAssertionJWT_ClientSecret(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *structs.ACLAuthMethodConfig
|
||||
wantErr bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "valid client secret",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "HS256",
|
||||
Audience: []string{"test-audience"},
|
||||
ExtraHeaders: map[string]string{
|
||||
"test-header": "test-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nil config",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "test-client-secret",
|
||||
OIDCClientAssertion: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: `no auth method config or client assertion`,
|
||||
},
|
||||
{
|
||||
name: "invalid client secret length",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "test-client-secret",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "HS256",
|
||||
Audience: []string{"test-audience"},
|
||||
ExtraHeaders: map[string]string{
|
||||
"test-header": "test-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: `invalid secret length for algorithm: "HS256" must be at least 32 bytes long`,
|
||||
},
|
||||
{
|
||||
name: "invalid client secret kid in extra header",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "HS256",
|
||||
Audience: []string{"test-audience"},
|
||||
ExtraHeaders: map[string]string{
|
||||
"kid": "test-kid",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: `WithHeaders: "kid" not allowed in WithHeaders; use WithKeyID instead`,
|
||||
},
|
||||
{
|
||||
name: "invalid key algorithm none",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "none",
|
||||
Audience: []string{"test-audience"},
|
||||
ExtraHeaders: map[string]string{
|
||||
"test-header": "test-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: `unsupported algorithm "none" for client secret`,
|
||||
},
|
||||
{
|
||||
name: "invalid key algorithm None",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "None",
|
||||
Audience: []string{"test-audience"},
|
||||
ExtraHeaders: map[string]string{
|
||||
"test-header": "test-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: `unsupported algorithm "None" for client secret`,
|
||||
},
|
||||
// expected non-nil error; got nil
|
||||
{
|
||||
name: "invalid missing audience",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientSecret: "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceClientSecret,
|
||||
KeyAlgorithm: "HS256",
|
||||
ExtraHeaders: map[string]string{
|
||||
"test-header": "test-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "missing Audience",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.config.Canonicalize() // inherits ClientSecret from OIDCClientAssertion
|
||||
jwt, err := BuildClientAssertionJWT(tt.config, nil, "")
|
||||
if tt.wantErr {
|
||||
must.Error(t, err)
|
||||
must.StrContains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, jwt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildClientAssertionJWT_PrivateKey(t *testing.T) {
|
||||
nomadKey := generateTestPrivateKey(t)
|
||||
nomadKeyPath := writeTestPrivateKeyToFile(t, nomadKey)
|
||||
nomadKID := "anything"
|
||||
nomadCert := generateTestCertificate(t, nomadKey)
|
||||
nomadCertPath := writeTestCertToFile(t, nomadCert)
|
||||
nonKeyCertFile := path.Join(t.TempDir(), "bad.key.cert.pem")
|
||||
must.NoError(t, os.WriteFile(nonKeyCertFile, []byte("not a key or cert"), 0644))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *structs.ACLAuthMethodConfig
|
||||
wantErr bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "nil config",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientAssertion: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid private key source with pem key",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadKey),
|
||||
KeyID: nomadKID,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid private key source with pem key with pem cert",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadKey),
|
||||
PemCert: encodeTestCert(nomadCert),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid private key source with pem cert file",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadKey),
|
||||
PemCertFile: nomadCertPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid private key source with pem key file with Key ID",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKeyFile: nomadKeyPath,
|
||||
KeyID: nomadKID,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
// invalid pem key file location
|
||||
{
|
||||
name: "invalid private key source with pem key file",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKeyFile: nomadKeyPath + "/invalid",
|
||||
KeyID: nomadKID,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "error reading PemKeyFile",
|
||||
},
|
||||
// file does exist but is not an rsa key
|
||||
{
|
||||
name: "invalid private key file contents",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKeyFile: nonKeyCertFile,
|
||||
KeyID: nomadKID,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "error parsing PemKeyFile: invalid key:",
|
||||
},
|
||||
{
|
||||
name: "invalid certificate file contents",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadKey),
|
||||
PemCertFile: nonKeyCertFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "failed to decode PemCertFile PEM block",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.config.Canonicalize() // inherits clientSecret from OIDCClientAssertion
|
||||
jwt, err := BuildClientAssertionJWT(tt.config, nomadKey, nomadKID)
|
||||
if tt.wantErr {
|
||||
must.Error(t, err)
|
||||
must.StrContains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, jwt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildClientAssertionJWT_NomadKey(t *testing.T) {
|
||||
nomadKey := generateTestPrivateKey(t)
|
||||
nomadKID := "anything"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *structs.ACLAuthMethodConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil config",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientAssertion: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "nomad key source",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourceNomad,
|
||||
KeyAlgorithm: "RS256",
|
||||
Audience: []string{"test-audience"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.config.Canonicalize() // inherits clientSecret from OIDCClientAssertion
|
||||
jwt, err := BuildClientAssertionJWT(tt.config, nomadKey, nomadKID)
|
||||
if tt.wantErr {
|
||||
must.Error(t, err)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, jwt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildClientAssertionJWT_PrivateKeyExpiredCert(t *testing.T) {
|
||||
nomadKey := generateTestPrivateKey(t)
|
||||
nomadInvalidKey := generateInvalidTestPrivateKey(t)
|
||||
nomadKID := "anything"
|
||||
nomadCert := generateTestCertificate(t, nomadKey)
|
||||
nomadExpiredCert := generateExpiredTestCertificate(t, nomadKey)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *structs.ACLAuthMethodConfig
|
||||
wantErr bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid PemKey",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadInvalidKey),
|
||||
PemCert: encodeTestCert(nomadCert),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "failed to parse private key",
|
||||
},
|
||||
{
|
||||
name: "expired certificate PemCert",
|
||||
config: &structs.ACLAuthMethodConfig{
|
||||
OIDCClientID: "test-client-id",
|
||||
OIDCClientAssertion: &structs.OIDCClientAssertion{
|
||||
KeySource: structs.OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
KeyAlgorithm: "RS256",
|
||||
PrivateKey: &structs.OIDCClientAssertionKey{
|
||||
PemKey: encodeTestPrivateKey(nomadKey),
|
||||
PemCert: encodeTestCert(nomadExpiredCert),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
expectedErr: "certificate has expired or is not yet valid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.config.Canonicalize() // inherits clientSecret from OIDCClientAssertion
|
||||
jwt, err := BuildClientAssertionJWT(tt.config, nomadKey, nomadKID)
|
||||
if tt.wantErr {
|
||||
must.Error(t, err)
|
||||
must.StrContains(t, err.Error(), tt.expectedErr)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
must.NotNil(t, jwt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateTestPrivateKey(t *testing.T) *rsa.PrivateKey {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
must.NoError(t, err)
|
||||
return key
|
||||
}
|
||||
|
||||
func writeTestPrivateKeyToFile(t *testing.T, key *rsa.PrivateKey) string {
|
||||
tmpDir := t.TempDir()
|
||||
keyPath := filepath.Join(tmpDir, "testkey.pem")
|
||||
|
||||
keyFile, err := os.Create(keyPath)
|
||||
must.NoError(t, err)
|
||||
defer keyFile.Close()
|
||||
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
block := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
err = pem.Encode(keyFile, block)
|
||||
must.NoError(t, err)
|
||||
|
||||
return keyPath
|
||||
}
|
||||
|
||||
func encodeTestPrivateKey(key *rsa.PrivateKey) string {
|
||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
block := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
return string(pem.EncodeToMemory(block))
|
||||
}
|
||||
|
||||
func generateTestCertificate(t *testing.T, key *rsa.PrivateKey) *x509.Certificate {
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
must.NoError(t, err)
|
||||
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
must.NoError(t, err)
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func encodeTestCert(cert *x509.Certificate) string {
|
||||
block := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
return string(pem.EncodeToMemory(block))
|
||||
}
|
||||
|
||||
func writeTestCertToFile(t *testing.T, cert *x509.Certificate) string {
|
||||
tmpDir := t.TempDir()
|
||||
certPath := filepath.Join(tmpDir, "testcert.pem")
|
||||
|
||||
certFile, err := os.Create(certPath)
|
||||
must.NoError(t, err)
|
||||
defer certFile.Close()
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
err = pem.Encode(certFile, block)
|
||||
must.NoError(t, err)
|
||||
|
||||
return certPath
|
||||
}
|
||||
|
||||
func generateExpiredTestCertificate(t *testing.T, key *rsa.PrivateKey) *x509.Certificate {
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now().Add(-2 * time.Hour),
|
||||
NotAfter: time.Now().Add(-1 * time.Hour),
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
must.NoError(t, err)
|
||||
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
must.NoError(t, err)
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func generateInvalidTestPrivateKey(t *testing.T) *rsa.PrivateKey {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
must.NoError(t, err)
|
||||
|
||||
// Simulate an invalid key by modifying the key's modulus
|
||||
key.N = big.NewInt(0) // This is just a placeholder to simulate an invalid key
|
||||
|
||||
return key
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -1378,11 +1379,13 @@ func (k *OIDCClientAssertionKey) Canonicalize() {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingClientAssertionKey = errors.New("missing PemKey or PemKeyFile")
|
||||
ErrAmbiguousClientAssertionKey = errors.New("require only one of PemKey or PemKeyFile")
|
||||
ErrMissingClientAssertionKeyID = errors.New("missing PemCert, PemCertFile, or KeyID")
|
||||
ErrAmbiguousClientAssertionKeyID = errors.New("require only one of PemCert, PemCertFile, or KeyID")
|
||||
ErrInvalidKeyIDHeader = errors.New("invalid KeyIDHeader")
|
||||
ErrMissingClientAssertionKey = errors.New("missing PemKey or PemKeyFile")
|
||||
ErrAmbiguousClientAssertionKey = errors.New("require only one of PemKey or PemKeyFile")
|
||||
ErrMissingClientAssertionKeyID = errors.New("missing PemCert, PemCertFile, or KeyID")
|
||||
ErrAmbiguousClientAssertionKeyID = errors.New("require only one of PemCert, PemCertFile, or KeyID")
|
||||
ErrInvalidClientAssertionKeyPath = errors.New("invalid PemKeyFile")
|
||||
ErrInvalidClientAssertionCertPath = errors.New("invalid PemCertFile")
|
||||
ErrInvalidKeyIDHeader = errors.New("invalid KeyIDHeader")
|
||||
)
|
||||
|
||||
// Validate ensures that one Key and one Cert or KeyID are provided,
|
||||
@@ -1400,6 +1403,11 @@ func (k *OIDCClientAssertionKey) Validate() error {
|
||||
if k.PemKey != "" && k.PemKeyFile != "" {
|
||||
return ErrAmbiguousClientAssertionKey
|
||||
}
|
||||
if k.PemKeyFile != "" {
|
||||
if !path.IsAbs(k.PemKeyFile) {
|
||||
return fmt.Errorf("%w: must be absolute; got: %s", ErrInvalidClientAssertionKeyPath, k.PemKeyFile)
|
||||
}
|
||||
}
|
||||
|
||||
// mutually exclusive cert fields
|
||||
// must have exactly one of: cert file or base64, or keyid
|
||||
@@ -1415,6 +1423,11 @@ func (k *OIDCClientAssertionKey) Validate() error {
|
||||
if k.KeyID != "" && (k.PemCert != "" || k.PemCertFile != "") {
|
||||
return ErrAmbiguousClientAssertionKeyID
|
||||
}
|
||||
if k.PemCertFile != "" {
|
||||
if !path.IsAbs(k.PemCertFile) {
|
||||
return fmt.Errorf("%w: must be absolute; got: %s", ErrInvalidClientAssertionCertPath, k.PemCertFile)
|
||||
}
|
||||
}
|
||||
|
||||
// only allow certain key id headers
|
||||
// only "kid" for KeyID
|
||||
|
||||
@@ -1392,6 +1392,22 @@ func TestACLAuthMethod_Merge(t *testing.T) {
|
||||
must.Eq(t, am1.Config.OIDCClientAssertion.PrivateKey.KeyID, "test-key-id")
|
||||
}
|
||||
|
||||
func TestACLAuthMethodConfig_Validate(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
// fail everything
|
||||
am := &ACLAuthMethodConfig{}
|
||||
am.Canonicalize() // there is insufficient info for this to help
|
||||
err := am.Validate()
|
||||
must.ErrorContains(t, err, "missing OIDCDiscoveryURL")
|
||||
must.ErrorContains(t, err, "missing OIDCClientID")
|
||||
must.ErrorContains(t, err, "missing BoundAudiences") // TODO: BoundIssuer? it's not even documented...
|
||||
am.OIDCClientAssertion = &OIDCClientAssertion{}
|
||||
am.Canonicalize()
|
||||
err = am.Validate()
|
||||
must.ErrorContains(t, err, "invalid client assertion config:")
|
||||
}
|
||||
|
||||
func TestACLAuthMethodConfig_Copy(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
@@ -1617,13 +1633,6 @@ func TestOIDCClientAssertionKey_Validate(t *testing.T) {
|
||||
},
|
||||
err: ErrMissingClientAssertionKey,
|
||||
},
|
||||
{
|
||||
name: "missing kid or cert",
|
||||
key: &OIDCClientAssertionKey{
|
||||
PemKeyFile: "/any.key",
|
||||
},
|
||||
err: ErrMissingClientAssertionKeyID,
|
||||
},
|
||||
{
|
||||
name: "ambiguous key",
|
||||
key: &OIDCClientAssertionKey{
|
||||
@@ -1632,6 +1641,13 @@ func TestOIDCClientAssertionKey_Validate(t *testing.T) {
|
||||
},
|
||||
err: ErrAmbiguousClientAssertionKey,
|
||||
},
|
||||
{
|
||||
name: "missing keyid or cert",
|
||||
key: &OIDCClientAssertionKey{
|
||||
PemKeyFile: "/any.key",
|
||||
},
|
||||
err: ErrMissingClientAssertionKeyID,
|
||||
},
|
||||
{
|
||||
name: "ambiguous keyid - cert file and pem",
|
||||
key: &OIDCClientAssertionKey{
|
||||
@@ -1659,6 +1675,22 @@ func TestOIDCClientAssertionKey_Validate(t *testing.T) {
|
||||
},
|
||||
err: ErrAmbiguousClientAssertionKeyID,
|
||||
},
|
||||
{
|
||||
name: "non-absolute key path",
|
||||
key: &OIDCClientAssertionKey{
|
||||
PemKeyFile: "./who-knows-where-this-might-be.key",
|
||||
KeyID: "key-id",
|
||||
},
|
||||
err: ErrInvalidClientAssertionKeyPath,
|
||||
},
|
||||
{
|
||||
name: "non-absolute cert path",
|
||||
key: &OIDCClientAssertionKey{
|
||||
PemKey: "anykey",
|
||||
PemCertFile: "./who-knows-where-this-might-be.cert",
|
||||
},
|
||||
err: ErrInvalidClientAssertionCertPath,
|
||||
},
|
||||
{
|
||||
name: "bad key header for keyid",
|
||||
key: &OIDCClientAssertionKey{
|
||||
@@ -2089,7 +2121,7 @@ func validClientAssertion() *OIDCClientAssertion {
|
||||
KeySource: OIDCKeySourcePrivateKey,
|
||||
Audience: []string{"test-audience"},
|
||||
PrivateKey: &OIDCClientAssertionKey{
|
||||
PemKeyFile: "test-key-file",
|
||||
PemKeyFile: "/test.key",
|
||||
KeyIDHeader: OIDCClientAssertionHeaderKid,
|
||||
KeyID: "test-key-id",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user