Skip to content

Commit

Permalink
Expose JWKS cluster config through JWTProviderConfigEntry (#17978) (#…
Browse files Browse the repository at this point in the history
…18002)

* Expose JWKS cluster config through JWTProviderConfigEntry

* fix typos, rename trustedCa to trustedCA
  • Loading branch information
roncodingenthusiast authored Jul 4, 2023
1 parent d4a9d1a commit 7408c14
Show file tree
Hide file tree
Showing 20 changed files with 1,384 additions and 322 deletions.
3 changes: 3 additions & 0 deletions .changelog/17978.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
mesh: Expose remote jwks cluster configuration through jwt-provider config entry
```
20 changes: 20 additions & 0 deletions agent/proxycfg/proxycfg.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ func (o *ConfigSnapshot) DeepCopy() *ConfigSnapshot {
*cp_JWTProviders_v2.JSONWebKeySet.Remote.RetryPolicy.RetryPolicyBackOff = *v2.JSONWebKeySet.Remote.RetryPolicy.RetryPolicyBackOff
}
}
if v2.JSONWebKeySet.Remote.JWKSCluster != nil {
cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster = new(structs.JWKSCluster)
*cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster = *v2.JSONWebKeySet.Remote.JWKSCluster
if v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates != nil {
cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates = new(structs.JWKSTLSCertificate)
*cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates = *v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates
if v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.CaCertificateProviderInstance != nil {
cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.CaCertificateProviderInstance = new(structs.JWKSTLSCertProviderInstance)
*cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.CaCertificateProviderInstance = *v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.CaCertificateProviderInstance
}
if v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA != nil {
cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA = new(structs.JWKSTLSCertTrustedCA)
*cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA = *v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA
if v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA.InlineBytes != nil {
cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA.InlineBytes = make([]byte, len(v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA.InlineBytes))
copy(cp_JWTProviders_v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA.InlineBytes, v2.JSONWebKeySet.Remote.JWKSCluster.TLSCertificates.TrustedCA.InlineBytes)
}
}
}
}
}
}
if v2.Audiences != nil {
Expand Down
137 changes: 132 additions & 5 deletions agent/structs/config_entry_jwt_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import (

const (
DefaultClockSkewSeconds = 30

DiscoveryTypeStrictDNS ClusterDiscoveryType = "STRICT_DNS"
DiscoveryTypeStatic ClusterDiscoveryType = "STATIC"
DiscoveryTypeLogicalDNS ClusterDiscoveryType = "LOGICAL_DNS"
DiscoveryTypeEDS ClusterDiscoveryType = "EDS"
DiscoveryTypeOriginalDST ClusterDiscoveryType = "ORIGINAL_DST"
)

type JWTProviderConfigEntry struct {
Expand Down Expand Up @@ -97,7 +103,7 @@ func (location *JWTLocation) Validate() error {
hasCookie := location.Cookie != nil

if countTrue(hasHeader, hasQueryParam, hasCookie) != 1 {
return fmt.Errorf("Must set exactly one of: JWT location header, query param or cookie")
return fmt.Errorf("must set exactly one of: JWT location header, query param or cookie")
}

if hasHeader {
Expand Down Expand Up @@ -205,7 +211,7 @@ func (ks *LocalJWKS) Validate() error {
hasJWKS := ks.JWKS != ""

if countTrue(hasFilename, hasJWKS) != 1 {
return fmt.Errorf("Must specify exactly one of String or filename for local keyset")
return fmt.Errorf("must specify exactly one of String or filename for local keyset")
}

if hasJWKS {
Expand Down Expand Up @@ -245,6 +251,9 @@ type RemoteJWKS struct {
//
// There is no retry by default.
RetryPolicy *JWKSRetryPolicy `json:",omitempty" alias:"retry_policy"`

// JWKSCluster defines how the specified Remote JWKS URI is to be fetched.
JWKSCluster *JWKSCluster `json:",omitempty" alias:"jwks_cluster"`
}

func (ks *RemoteJWKS) Validate() error {
Expand All @@ -257,9 +266,127 @@ func (ks *RemoteJWKS) Validate() error {
}

if ks.RetryPolicy != nil && ks.RetryPolicy.RetryPolicyBackOff != nil {
return ks.RetryPolicy.RetryPolicyBackOff.Validate()
err := ks.RetryPolicy.RetryPolicyBackOff.Validate()
if err != nil {
return err
}
}

if ks.JWKSCluster != nil {
return ks.JWKSCluster.Validate()
}

return nil
}

type JWKSCluster struct {
// DiscoveryType refers to the service discovery type to use for resolving the cluster.
//
// This defaults to STRICT_DNS.
// Other options include STATIC, LOGICAL_DNS, EDS or ORIGINAL_DST.
DiscoveryType ClusterDiscoveryType `json:",omitempty" alias:"discovery_type"`

// TLSCertificates refers to the data containing certificate authority certificates to use
// in verifying a presented peer certificate.
// If not specified and a peer certificate is presented it will not be verified.
//
// Must be either CaCertificateProviderInstance or TrustedCA.
TLSCertificates *JWKSTLSCertificate `json:",omitempty" alias:"tls_certificates"`

// The timeout for new network connections to hosts in the cluster.
// If not set, a default value of 5s will be used.
ConnectTimeout time.Duration `json:",omitempty" alias:"connect_timeout"`
}

type ClusterDiscoveryType string

func (d ClusterDiscoveryType) Validate() error {
switch d {
case DiscoveryTypeStatic, DiscoveryTypeStrictDNS, DiscoveryTypeLogicalDNS, DiscoveryTypeEDS, DiscoveryTypeOriginalDST:
return nil
default:
return fmt.Errorf("unsupported jwks cluster discovery type: %q", d)
}
}

func (c *JWKSCluster) Validate() error {
if c.DiscoveryType != "" {
err := c.DiscoveryType.Validate()
if err != nil {
return err
}
}

if c.TLSCertificates != nil {
return c.TLSCertificates.Validate()
}
return nil
}

// JWKSTLSCertificate refers to the data containing certificate authority certificates to use
// in verifying a presented peer certificate.
// If not specified and a peer certificate is presented it will not be verified.
//
// Must be either CaCertificateProviderInstance or TrustedCA.
type JWKSTLSCertificate struct {
// CaCertificateProviderInstance Certificate provider instance for fetching TLS certificates.
CaCertificateProviderInstance *JWKSTLSCertProviderInstance `json:",omitempty" alias:"ca_certificate_provider_instance"`

// TrustedCA defines TLS certificate data containing certificate authority certificates
// to use in verifying a presented peer certificate.
//
// Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified.
TrustedCA *JWKSTLSCertTrustedCA `json:",omitempty" alias:"trusted_ca"`
}

func (c *JWKSTLSCertificate) Validate() error {
hasProviderInstance := c.CaCertificateProviderInstance != nil
hasTrustedCA := c.TrustedCA != nil

if countTrue(hasProviderInstance, hasTrustedCA) != 1 {
return fmt.Errorf("must specify exactly one of: CaCertificateProviderInstance or TrustedCA for JKWS' TLSCertificates")
}

if c.TrustedCA != nil {
return c.TrustedCA.Validate()
}
return nil
}

type JWKSTLSCertProviderInstance struct {
// InstanceName refers to the certificate provider instance name
//
// The default value is "default".
InstanceName string `json:",omitempty" alias:"instance_name"`

// CertificateName is used to specify certificate instances or types. For example, "ROOTCA" to specify
// a root-certificate (validation context) or "example.com" to specify a certificate for a
// particular domain.
//
// The default value is the empty string.
CertificateName string `json:",omitempty" alias:"certificate_name"`
}

// JWKSTLSCertTrustedCA defines TLS certificate data containing certificate authority certificates
// to use in verifying a presented peer certificate.
//
// Exactly one of Filename, EnvironmentVariable, InlineString or InlineBytes must be specified.
type JWKSTLSCertTrustedCA struct {
Filename string `json:",omitempty" alias:"filename"`
EnvironmentVariable string `json:",omitempty" alias:"environment_variable"`
InlineString string `json:",omitempty" alias:"inline_string"`
InlineBytes []byte `json:",omitempty" alias:"inline_bytes"`
}

func (c *JWKSTLSCertTrustedCA) Validate() error {
hasFilename := c.Filename != ""
hasEnv := c.EnvironmentVariable != ""
hasInlineBytes := len(c.InlineBytes) > 0
hasInlineString := c.InlineString != ""

if countTrue(hasFilename, hasEnv, hasInlineString, hasInlineBytes) != 1 {
return fmt.Errorf("must specify exactly one of: Filename, EnvironmentVariable, InlineString or InlineBytes for JWKS' TrustedCA")
}
return nil
}

Expand Down Expand Up @@ -293,7 +420,7 @@ type RetryPolicyBackOff struct {
func (r *RetryPolicyBackOff) Validate() error {

if (r.MaxInterval != 0) && (r.BaseInterval > r.MaxInterval) {
return fmt.Errorf("Retry policy backoff's MaxInterval should be greater or equal to BaseInterval")
return fmt.Errorf("retry policy backoff's MaxInterval should be greater or equal to BaseInterval")
}

return nil
Expand Down Expand Up @@ -339,7 +466,7 @@ func (jwks *JSONWebKeySet) Validate() error {
hasRemoteKeySet := jwks.Remote != nil

if countTrue(hasLocalKeySet, hasRemoteKeySet) != 1 {
return fmt.Errorf("Must specify exactly one of Local or Remote JSON Web key set")
return fmt.Errorf("must specify exactly one of Local or Remote JSON Web key set")
}

if hasRemoteKeySet {
Expand Down
87 changes: 81 additions & 6 deletions agent/structs/config_entry_jwt_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func newTestAuthz(t *testing.T, src string) acl.Authorizer {

var tenSeconds time.Duration = 10 * time.Second
var hundredSeconds time.Duration = 100 * time.Second
var connectTimeout = time.Duration(5) * time.Second

func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
defaultMeta := DefaultEnterpriseMetaInDefaultPartition()
Expand Down Expand Up @@ -113,7 +114,7 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
Name: "okta",
JSONWebKeySet: &JSONWebKeySet{},
},
validateErr: "Must specify exactly one of Local or Remote JSON Web key set",
validateErr: "must specify exactly one of Local or Remote JSON Web key set",
},
"invalid jwt-provider - local jwks with non-encoded base64 jwks": {
entry: &JWTProviderConfigEntry{
Expand All @@ -138,7 +139,7 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
Remote: &RemoteJWKS{},
},
},
validateErr: "Must specify exactly one of Local or Remote JSON Web key set",
validateErr: "must specify exactly one of Local or Remote JSON Web key set",
},
"invalid jwt-provider - local jwks string and filename both set": {
entry: &JWTProviderConfigEntry{
Expand All @@ -151,7 +152,7 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
},
},
},
validateErr: "Must specify exactly one of String or filename for local keyset",
validateErr: "must specify exactly one of String or filename for local keyset",
},
"invalid jwt-provider - remote jwks missing uri": {
entry: &JWTProviderConfigEntry{
Expand Down Expand Up @@ -202,7 +203,7 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
},
},
},
validateErr: "Must set exactly one of: JWT location header, query param or cookie",
validateErr: "must set exactly one of: JWT location header, query param or cookie",
},
"invalid jwt-provider - Remote JWKS retry policy maxinterval < baseInterval": {
entry: &JWTProviderConfigEntry{
Expand All @@ -221,7 +222,63 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
},
},
},
validateErr: "Retry policy backoff's MaxInterval should be greater or equal to BaseInterval",
validateErr: "retry policy backoff's MaxInterval should be greater or equal to BaseInterval",
},
"invalid jwt-provider - Remote JWKS cluster wrong discovery type": {
entry: &JWTProviderConfigEntry{
Kind: JWTProvider,
Name: "okta",
JSONWebKeySet: &JSONWebKeySet{
Remote: &RemoteJWKS{
FetchAsynchronously: true,
URI: "https://example.com/.well-known/jwks.json",
JWKSCluster: &JWKSCluster{
DiscoveryType: "FAKE",
},
},
},
},
validateErr: "unsupported jwks cluster discovery type: \"FAKE\"",
},
"invalid jwt-provider - Remote JWKS cluster with both trustedCa and provider instance": {
entry: &JWTProviderConfigEntry{
Kind: JWTProvider,
Name: "okta",
JSONWebKeySet: &JSONWebKeySet{
Remote: &RemoteJWKS{
FetchAsynchronously: true,
URI: "https://example.com/.well-known/jwks.json",
JWKSCluster: &JWKSCluster{
TLSCertificates: &JWKSTLSCertificate{
TrustedCA: &JWKSTLSCertTrustedCA{},
CaCertificateProviderInstance: &JWKSTLSCertProviderInstance{},
},
},
},
},
},
validateErr: "must specify exactly one of: CaCertificateProviderInstance or TrustedCA for JKWS' TLSCertificates",
},
"invalid jwt-provider - Remote JWKS cluster with multiple trustedCa options": {
entry: &JWTProviderConfigEntry{
Kind: JWTProvider,
Name: "okta",
JSONWebKeySet: &JSONWebKeySet{
Remote: &RemoteJWKS{
FetchAsynchronously: true,
URI: "https://example.com/.well-known/jwks.json",
JWKSCluster: &JWKSCluster{
TLSCertificates: &JWKSTLSCertificate{
TrustedCA: &JWKSTLSCertTrustedCA{
Filename: "myfile.cert",
InlineString: "*****",
},
},
},
},
},
},
validateErr: "must specify exactly one of: Filename, EnvironmentVariable, InlineString or InlineBytes for JWKS' TrustedCA",
},
"invalid jwt-provider - JWT location with 2 fields": {
entry: &JWTProviderConfigEntry{
Expand All @@ -244,7 +301,7 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
},
},
},
validateErr: "Must set exactly one of: JWT location header, query param or cookie",
validateErr: "must set exactly one of: JWT location header, query param or cookie",
},
"valid jwt-provider - with all possible fields": {
entry: &JWTProviderConfigEntry{
Expand All @@ -265,6 +322,15 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
MaxInterval: hundredSeconds,
},
},
JWKSCluster: &JWKSCluster{
DiscoveryType: "STATIC",
ConnectTimeout: connectTimeout,
TLSCertificates: &JWKSTLSCertificate{
TrustedCA: &JWKSTLSCertTrustedCA{
Filename: "myfile.cert",
},
},
},
},
},
Forwarding: &JWTForwardingConfig{
Expand Down Expand Up @@ -297,6 +363,15 @@ func TestJWTProviderConfigEntry_ValidateAndNormalize(t *testing.T) {
MaxInterval: hundredSeconds,
},
},
JWKSCluster: &JWKSCluster{
DiscoveryType: "STATIC",
ConnectTimeout: connectTimeout,
TLSCertificates: &JWKSTLSCertificate{
TrustedCA: &JWKSTLSCertTrustedCA{
Filename: "myfile.cert",
},
},
},
},
},
Forwarding: &JWTForwardingConfig{
Expand Down
Loading

0 comments on commit 7408c14

Please sign in to comment.