Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NET-5346] Expose JWKCluster fields in jwt-provider config entry #2881

Merged
merged 1 commit into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/2881.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
helm: Add `JWKSCluster` field to `JWTProvider` CRD.
```
60 changes: 60 additions & 0 deletions charts/consul/templates/crd-jwtproviders.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,66 @@ spec:
the proxy listener will wait for the JWKS to be fetched
before being activated. \n Default value is false."
type: boolean
jwksCluster:
description: "JWKSCluster defines how the specified Remote JWKS
URI is to be fetched."
properties:
connectTimeout:
description: "The timeout for new network connections to hosts
in the cluster. \n If not set, a default value of 5s will be
used."
format: int64
type: integer
discoveryType:
description: "DiscoveryType refers to the service discovery type
to use for resolving the cluster. \n Defaults to STRICT_DNS."
type: string
tlsCertificates:
description: "TLSCertificates refers to the data containing
certificate authority certificates to use in verifying a presented
peer certificate."
properties:
caCertificateProviderInstance:
description: "CaCertificateProviderInstance Certificate provider
instance for fetching TLS certificates."
properties:
instanceName:
description: "InstanceName refers to the certificate provider
instance name. \n The default value is 'default'."
type: string
certificateName:
description: "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. \n
The default value is the empty string."
type: string
type: object
trustedCA:
description: "TrustedCA defines TLS certificate data containing
certificate authority certificates to use in verifying a presented
peer certificate. \n Exactly one of Filename, EnvironmentVariable,
InlineString or InlineBytes must be specified."
properties:
filename:
description: "The name of the file on the local system to use a
data source for trusted CA certificates."
type: string
environmentVariable:
description: "The environment variable on the local system to use
a data source for trusted CA certificates."
type: string
inlineString:
description: "A string to inline in the configuration for use as
a data source for trusted CA certificates."
type: string
inlineBytes:
description: "A sequence of bytes to inline in the configuration
for use as a data source for trusted CA certificates."
type: string
type: object
type: object
type: object
requestTimeoutMs:
description: RequestTimeoutMs is the number of milliseconds
to time out when making a request for the JWKS.
Expand Down
203 changes: 197 additions & 6 deletions control-plane/api/v1alpha1/jwtprovider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ import (
)

const (
JWTProviderKubeKind string = "jwtprovider"
JWTProviderKubeKind string = "jwtprovider"
DiscoveryTypeStrictDNS ClusterDiscoveryType = "STRICT_DNS"
DiscoveryTypeStatic ClusterDiscoveryType = "STATIC"
DiscoveryTypeLogicalDNS ClusterDiscoveryType = "LOGICAL_DNS"
DiscoveryTypeEDS ClusterDiscoveryType = "EDS"
DiscoveryTypeOriginalDST ClusterDiscoveryType = "ORIGINAL_DST"
)

func init() {
Expand Down Expand Up @@ -404,6 +409,9 @@ type RemoteJWKS struct {
//
// There is no retry by default.
RetryPolicy *JWKSRetryPolicy `json:"retryPolicy,omitempty"`

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

func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS {
Expand All @@ -416,6 +424,7 @@ func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS {
CacheDuration: r.CacheDuration,
FetchAsynchronously: r.FetchAsynchronously,
RetryPolicy: r.RetryPolicy.toConsul(),
JWKSCluster: r.JWKSCluster.toConsul(),
}
}

Expand All @@ -432,9 +441,188 @@ func (r *RemoteJWKS) validate(path *field.Path) field.ErrorList {
}

errs = append(errs, r.RetryPolicy.validate(path.Child("retryPolicy"))...)
errs = append(errs, r.JWKSCluster.validate(path.Child("jwksCluster"))...)
return errs
}

// JWKSCluster defines how the specified Remote JWKS URI is to be fetched.
type JWKSCluster struct {
roncodingenthusiast marked this conversation as resolved.
Show resolved Hide resolved
// 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:"discoveryType,omitempty"`

// 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:"tlsCertificates,omitempty"`

// 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:"connectTimeout,omitempty"`
}

func (c *JWKSCluster) toConsul() *capi.JWKSCluster {
if c == nil {
return nil
}
return &capi.JWKSCluster{
DiscoveryType: c.DiscoveryType.toConsul(),
TLSCertificates: c.TLSCertificates.toConsul(),
ConnectTimeout: c.ConnectTimeout,
}
}

func (c *JWKSCluster) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
if c == nil {
return errs
}

errs = append(errs, c.DiscoveryType.validate(path.Child("discoveryType"))...)
errs = append(errs, c.TLSCertificates.validate(path.Child("tlsCertificates"))...)

return errs
}

type ClusterDiscoveryType string

func (d ClusterDiscoveryType) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList

switch d {
case DiscoveryTypeStatic, DiscoveryTypeStrictDNS, DiscoveryTypeLogicalDNS, DiscoveryTypeEDS, DiscoveryTypeOriginalDST:
return errs
default:
errs = append(errs, field.Invalid(path, string(d), "unsupported jwks cluster discovery type."))
}
return errs
}

func (d ClusterDiscoveryType) toConsul() capi.ClusterDiscoveryType {
return capi.ClusterDiscoveryType(string(d))
}

// 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:"caCertificateProviderInstance,omitempty"`

// 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:"trustedCA,omitempty"`
}

func (c *JWKSTLSCertificate) toConsul() *capi.JWKSTLSCertificate {
if c == nil {
return nil
}

return &capi.JWKSTLSCertificate{
TrustedCA: c.TrustedCA.toConsul(),
CaCertificateProviderInstance: c.CaCertificateProviderInstance.toConsul(),
}
}

func (c *JWKSTLSCertificate) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
if c == nil {
return errs
}

hasProviderInstance := c.CaCertificateProviderInstance != nil
hasTrustedCA := c.TrustedCA != nil

if countTrue(hasTrustedCA, hasProviderInstance) != 1 {
asJSON, _ := json.Marshal(c)
errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'trustedCa' or 'caCertificateProviderInstance' is required"))
}

errs = append(errs, c.TrustedCA.validate(path.Child("trustedCa"))...)

return errs
}

// JWKSTLSCertProviderInstance Certificate provider instance for fetching TLS certificates.
type JWKSTLSCertProviderInstance struct {
roncodingenthusiast marked this conversation as resolved.
Show resolved Hide resolved
// InstanceName refers to the certificate provider instance name.
//
// The default value is "default".
InstanceName string `json:"instanceName,omitempty"`

// 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:"certificateName,omitempty"`
}

func (c *JWKSTLSCertProviderInstance) toConsul() *capi.JWKSTLSCertProviderInstance {
if c == nil {
return nil
}

return &capi.JWKSTLSCertProviderInstance{
InstanceName: c.InstanceName,
CertificateName: c.CertificateName,
}
}

// 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:"filename,omitempty"`
EnvironmentVariable string `json:"environmentVariable,omitempty"`
InlineString string `json:"inlineString,omitempty"`
InlineBytes []byte `json:"inlineBytes,omitempty"`
}

func (c *JWKSTLSCertTrustedCA) toConsul() *capi.JWKSTLSCertTrustedCA {
if c == nil {
return nil
}

return &capi.JWKSTLSCertTrustedCA{
Filename: c.Filename,
EnvironmentVariable: c.EnvironmentVariable,
InlineBytes: c.InlineBytes,
InlineString: c.InlineString,
}
}

func (c *JWKSTLSCertTrustedCA) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
if c == nil {
return errs
}

hasFilename := c.Filename != ""
hasEnv := c.EnvironmentVariable != ""
hasInlineBytes := len(c.InlineBytes) > 0
hasInlineString := c.InlineString != ""

if countTrue(hasFilename, hasEnv, hasInlineString, hasInlineBytes) != 1 {
asJSON, _ := json.Marshal(c)
errs = append(errs, field.Invalid(path, string(asJSON), "exactly one of 'filename', 'environmentVariable', 'inlineString' or 'inlineBytes' is required"))
}
return errs
}

// JWKSRetryPolicy defines a retry policy for fetching JWKS.
//
// There is no retry by default.
type JWKSRetryPolicy struct {
// NumRetries is the number of times to retry fetching the JWKS.
// The retry strategy uses jittered exponential backoff with
Expand All @@ -443,9 +631,9 @@ type JWKSRetryPolicy struct {
// Default value is 0.
NumRetries int `json:"numRetries,omitempty"`

// Backoff policy
// Retry's backoff policy.
//
// Defaults to Envoy's backoff policy
// Defaults to Envoy's backoff policy.
RetryPolicyBackOff *RetryPolicyBackOff `json:"retryPolicyBackOff,omitempty"`
}

Expand All @@ -468,16 +656,19 @@ func (j *JWKSRetryPolicy) validate(path *field.Path) field.ErrorList {
return append(errs, j.RetryPolicyBackOff.validate(path.Child("retryPolicyBackOff"))...)
}

// RetryPolicyBackOff defines retry's policy backoff.
//
// Defaults to Envoy's backoff policy.
type RetryPolicyBackOff struct {
// BaseInterval to be used for the next back off computation
// BaseInterval to be used for the next back off computation.
//
// The default value from envoy is 1s
// The default value from envoy is 1s.
BaseInterval time.Duration `json:"baseInterval,omitempty"`

// MaxInternal to be used to specify the maximum interval between retries.
// Optional but should be greater or equal to BaseInterval.
//
// Defaults to 10 times BaseInterval
// Defaults to 10 times BaseInterval.
MaxInterval time.Duration `json:"maxInterval,omitempty"`
}

Expand Down
Loading