mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-08-15 11:46:12 +00:00
Add .Status to JWTAuthenticator with Conditions,Phase
- "Ready" condition & supporting conditions - Legacy "Phase" for convenience - Refactor newCachedJWTAuthenticator() func to improve ability to provide additional conditions - Update JWTAuthenticator.Status type - Update RBAC for SA to get/watch/update JWTAuthenticator.Status - Update logger to plog, add tests for logs & statuses - update Sync() to reduce enqueue when error is config/user managed, perhaps remove validateJWKSResponse()
This commit is contained in:
committed by
Ryan Richard
parent
f18d7310a8
commit
a4447fa606
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
@@ -43,6 +43,10 @@ rules:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators, webhookauthenticators ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators/status, webhookauthenticators/status ]
|
||||
verbs: [ get, list, watch, update ]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -180,6 +180,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -180,6 +180,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -46,6 +46,18 @@ JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase"]
|
||||
==== JWTAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
|
||||
==== JWTAuthenticatorSpec
|
||||
|
||||
@@ -80,6 +92,7 @@ Status of a JWT authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-jwtauthenticatorphase[$$JWTAuthenticatorPhase$$]__ | Phase summarizes the overall status of the JWTAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
+8
@@ -174,6 +174,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
+17
@@ -5,6 +5,19 @@ package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type JWTAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
|
||||
@@ -7,38 +7,74 @@ package jwtcachefiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
errorsutil "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
authinformers "go.pinniped.dev/generated/latest/client/concierge/informers/externalversions/authentication/v1alpha1"
|
||||
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
||||
pinnipedauthenticator "go.pinniped.dev/internal/controller/authenticator"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controller/conditionsutil"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/net/phttp"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
// These default values come from the way that the Supervisor issues and signs tokens. We make these
|
||||
// the defaults for a JWTAuthenticator so that they can easily integrate with the Supervisor.
|
||||
const (
|
||||
controllerName = "jwtcachefiller-controller"
|
||||
|
||||
typeReady = "Ready"
|
||||
typeTLSConfigurationValid = "TLSConfigurationValid"
|
||||
typeIssuerURLValid = "IssuerURLValid"
|
||||
typeDiscoveryValid = "DiscoveryURLValid"
|
||||
typeJWKSURLValid = "JWKSURLValid"
|
||||
typeJWKSURLValidResponse = "JWKSURLValidResponse"
|
||||
typeAuthenticatorValid = "AuthenticatorValid"
|
||||
|
||||
reasonSuccess = "Success"
|
||||
reasonNotReady = "NotReady"
|
||||
reasonUnableToValidate = "UnableToValidate"
|
||||
reasonInvalidIssuerURL = "InvalidIssuerURL"
|
||||
reasonInvalidIssuerURLScheme = "InvalidIssuerURLScheme"
|
||||
reasonInvalidProviderJWKSURL = "InvalidProviderJWKSURL"
|
||||
reasonInvalidProviderJWKSURLScheme = "InvalidProviderJWKSURLScheme"
|
||||
reasonInvalidProviderJWKResponse = "InvalidProviderJWKResponse"
|
||||
reasonInvalidTLSConfiguration = "InvalidTLSConfiguration"
|
||||
reasonInvalidDiscoveryProbe = "InvalidDiscoveryProbe"
|
||||
reasonInvalidAuthenticator = "InvalidAuthenticator"
|
||||
|
||||
msgUnableToValidate = "unable to validate; other issues present"
|
||||
|
||||
defaultUsernameClaim = oidcapi.IDTokenClaimUsername
|
||||
defaultGroupsClaim = oidcapi.IDTokenClaimGroups
|
||||
)
|
||||
|
||||
type providerJSON struct {
|
||||
JWKSURL string `json:"jwks_uri"`
|
||||
}
|
||||
|
||||
// defaultSupportedSigningAlgos returns the default signing algos that this JWTAuthenticator
|
||||
// supports (i.e., if none are supplied by the user).
|
||||
func defaultSupportedSigningAlgos() []string {
|
||||
@@ -58,7 +94,7 @@ type tokenAuthenticatorCloser interface {
|
||||
pinnipedauthenticator.Closer
|
||||
}
|
||||
|
||||
type jwtAuthenticator struct {
|
||||
type cachedJWTAuthenticator struct {
|
||||
tokenAuthenticatorCloser
|
||||
spec *auth1alpha1.JWTAuthenticatorSpec
|
||||
}
|
||||
@@ -66,16 +102,20 @@ type jwtAuthenticator struct {
|
||||
// New instantiates a new controllerlib.Controller which will populate the provided authncache.Cache.
|
||||
func New(
|
||||
cache *authncache.Cache,
|
||||
client conciergeclientset.Interface,
|
||||
jwtAuthenticators authinformers.JWTAuthenticatorInformer,
|
||||
log logr.Logger,
|
||||
clock clock.Clock,
|
||||
log plog.Logger,
|
||||
) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: "jwtcachefiller-controller",
|
||||
Syncer: &controller{
|
||||
Name: controllerName,
|
||||
Syncer: &jwtCacheFillerController{
|
||||
cache: cache,
|
||||
client: client,
|
||||
jwtAuthenticators: jwtAuthenticators,
|
||||
log: log.WithName("jwtcachefiller-controller"),
|
||||
clock: clock,
|
||||
log: log.WithName(controllerName),
|
||||
},
|
||||
},
|
||||
controllerlib.WithInformer(
|
||||
@@ -86,16 +126,19 @@ func New(
|
||||
)
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
type jwtCacheFillerController struct {
|
||||
cache *authncache.Cache
|
||||
jwtAuthenticators authinformers.JWTAuthenticatorInformer
|
||||
log logr.Logger
|
||||
client conciergeclientset.Interface
|
||||
clock clock.Clock
|
||||
log plog.Logger
|
||||
}
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
func (c *jwtCacheFillerController) Sync(ctx controllerlib.Context) error {
|
||||
obj, err := c.jwtAuthenticators.Lister().Get(ctx.Key.Name)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
c.log.Info("Sync() found that the JWTAuthenticator does not exist yet or was deleted")
|
||||
return nil
|
||||
}
|
||||
@@ -125,20 +168,46 @@ func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
conditions := make([]*metav1.Condition, 0)
|
||||
specCopy := obj.Spec.DeepCopy()
|
||||
var errs []error
|
||||
|
||||
rootCAs, conditions, tlsOk := c.validateTLS(specCopy, conditions)
|
||||
_, conditions, issuerOk := c.validateIssuer(specCopy.Issuer, conditions)
|
||||
|
||||
client := phttp.Default(rootCAs)
|
||||
client.Timeout = 30 * time.Second // copied from Kube OIDC code
|
||||
coreOSCtx := coreosoidc.ClientContext(context.Background(), client)
|
||||
|
||||
pJSON, provider, conditions, providerErr := c.validateProviderDiscovery(coreOSCtx, specCopy, conditions, tlsOk && issuerOk)
|
||||
errs = append(errs, providerErr)
|
||||
|
||||
jwksURL, conditions, jwksErr := c.validateProviderJWKSURL(provider, pJSON, conditions, tlsOk && issuerOk && providerErr == nil)
|
||||
errs = append(errs, jwksErr)
|
||||
|
||||
// Make a deep copy of the spec so we aren't storing pointers to something that the informer cache
|
||||
// may mutate!
|
||||
jwtAuthenticator, err := newJWTAuthenticator(obj.Spec.DeepCopy())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build jwt authenticator: %w", err)
|
||||
// may mutate! We don't store status as status is derived from spec.
|
||||
cachedAuthenticator, conditions, err := c.newCachedJWTAuthenticator(coreOSCtx, client, obj.Spec.DeepCopy(), jwksURL, conditions, tlsOk && issuerOk && providerErr == nil && jwksErr == nil)
|
||||
errs = append(errs, err)
|
||||
|
||||
if !hadErrorCondition(conditions) {
|
||||
c.cache.Store(cacheKey, cachedAuthenticator) // the in-memory cache must need the extra functionality
|
||||
c.log.WithValues("jwtAuthenticator", klog.KObj(obj), "issuer", obj.Spec.Issuer).Info("added new jwt authenticator")
|
||||
}
|
||||
|
||||
c.cache.Store(cacheKey, jwtAuthenticator)
|
||||
c.log.WithValues("jwtAuthenticator", klog.KObj(obj), "issuer", obj.Spec.Issuer).Info("added new jwt authenticator")
|
||||
return nil
|
||||
err = c.updateStatus(ctx.Context, obj, conditions)
|
||||
errs = append(errs, err)
|
||||
|
||||
// sync loop errors:
|
||||
// - should not be configuration errors. config errors a user must correct belong on the .Status
|
||||
// object. The controller simply must wait for a user to correct before running again.
|
||||
// - other errors, such as networking errors, etc. are the types of errors that should return here
|
||||
// and signal the controller to retry the sync loop. These may be corrected by machines.
|
||||
return errorsutil.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (c *controller) extractValueAsJWTAuthenticator(value authncache.Value) *jwtAuthenticator {
|
||||
jwtAuthenticator, ok := value.(*jwtAuthenticator)
|
||||
func (c *jwtCacheFillerController) extractValueAsJWTAuthenticator(value authncache.Value) *cachedJWTAuthenticator {
|
||||
jwtAuthenticator, ok := value.(*cachedJWTAuthenticator)
|
||||
if !ok {
|
||||
actualType := "<nil>"
|
||||
if t := reflect.TypeOf(value); t != nil {
|
||||
@@ -150,11 +219,55 @@ func (c *controller) extractValueAsJWTAuthenticator(value authncache.Value) *jwt
|
||||
return jwtAuthenticator
|
||||
}
|
||||
|
||||
// newJWTAuthenticator creates a jwt authenticator from the provided spec.
|
||||
func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthenticator, error) {
|
||||
rootCAs, _, err := pinnipedauthenticator.CABundle(spec.TLS)
|
||||
func (c *jwtCacheFillerController) updateStatus(
|
||||
ctx context.Context,
|
||||
original *auth1alpha1.JWTAuthenticator,
|
||||
conditions []*metav1.Condition,
|
||||
) error {
|
||||
updated := original.DeepCopy()
|
||||
|
||||
if hadErrorCondition(conditions) {
|
||||
updated.Status.Phase = auth1alpha1.JWTAuthenticatorPhaseError
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeReady,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonNotReady,
|
||||
Message: "the JWTAuthenticator is not ready: see other conditions for details",
|
||||
})
|
||||
} else {
|
||||
updated.Status.Phase = auth1alpha1.JWTAuthenticatorPhaseReady
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeReady,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: "the JWTAuthenticator is ready",
|
||||
})
|
||||
}
|
||||
|
||||
_ = conditionsutil.MergeConfigConditions(conditions,
|
||||
original.Generation, &updated.Status.Conditions, plog.New().WithName(controllerName), metav1.NewTime(c.clock.Now()))
|
||||
|
||||
if equality.Semantic.DeepEqual(original, updated) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := c.client.AuthenticationV1alpha1().JWTAuthenticators().UpdateStatus(ctx, updated, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid TLS configuration: %w", err)
|
||||
c.log.Info(fmt.Sprintf("ERROR: %v", err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newCachedJWTAuthenticator creates a jwt authenticator from the provided spec.
|
||||
func (c *jwtCacheFillerController) newCachedJWTAuthenticator(ctx context.Context, client *http.Client, spec *auth1alpha1.JWTAuthenticatorSpec, jwksURL string, conditions []*metav1.Condition, prereqOk bool) (*cachedJWTAuthenticator, []*metav1.Condition, error) {
|
||||
if !prereqOk {
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: reasonUnableToValidate,
|
||||
Message: msgUnableToValidate,
|
||||
})
|
||||
return nil, conditions, nil
|
||||
}
|
||||
|
||||
usernameClaim := spec.Claims.Username
|
||||
@@ -166,33 +279,6 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthentica
|
||||
groupsClaim = defaultGroupsClaim
|
||||
}
|
||||
|
||||
// copied from Kube OIDC code
|
||||
issuerURL, err := url.Parse(spec.Issuer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if issuerURL.Scheme != "https" {
|
||||
return nil, fmt.Errorf("issuer (%q) has invalid scheme (%q), require 'https'", spec.Issuer, issuerURL.Scheme)
|
||||
}
|
||||
|
||||
client := phttp.Default(rootCAs)
|
||||
client.Timeout = 30 * time.Second // copied from Kube OIDC code
|
||||
|
||||
ctx := coreosoidc.ClientContext(context.Background(), client)
|
||||
|
||||
provider, err := coreosoidc.NewProvider(ctx, spec.Issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize provider: %w", err)
|
||||
}
|
||||
providerJSON := &struct {
|
||||
JWKSURL string `json:"jwks_uri"`
|
||||
}{}
|
||||
if err := provider.Claims(providerJSON); err != nil {
|
||||
return nil, fmt.Errorf("could not get provider jwks_uri: %w", err) // should be impossible because coreosoidc.NewProvider validates this
|
||||
}
|
||||
if len(providerJSON.JWKSURL) == 0 {
|
||||
return nil, fmt.Errorf("issuer %q does not have jwks_uri set", spec.Issuer)
|
||||
}
|
||||
oidcAuthenticator, err := oidc.New(oidc.Options{
|
||||
JWTAuthenticator: apiserver.JWTAuthenticator{
|
||||
Issuer: apiserver.Issuer{
|
||||
@@ -210,16 +296,196 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthentica
|
||||
},
|
||||
},
|
||||
},
|
||||
KeySet: coreosoidc.NewRemoteKeySet(ctx, providerJSON.JWKSURL),
|
||||
KeySet: coreosoidc.NewRemoteKeySet(ctx, jwksURL),
|
||||
SupportedSigningAlgs: defaultSupportedSigningAlgos(),
|
||||
Client: client,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize authenticator: %w", err)
|
||||
// no unit test for this failure.
|
||||
// it seems that our production code doesn't provide config knobs that would allow
|
||||
// incorrect configuration of oidc.New(). We validate inputs before we get to this point
|
||||
// and exit early if there are problems. In the future, if we allow more configuration,
|
||||
// such as supported signing algorithm config, we may be able to test this.
|
||||
errText := "could not initialize oidc authenticator"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidAuthenticator,
|
||||
Message: msg,
|
||||
})
|
||||
// resync err, lots of possible issues that may or may not be machine related
|
||||
return nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
return &jwtAuthenticator{
|
||||
msg := "authenticator initialized"
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return &cachedJWTAuthenticator{
|
||||
tokenAuthenticatorCloser: oidcAuthenticator,
|
||||
spec: spec,
|
||||
}, nil
|
||||
}, conditions, nil
|
||||
}
|
||||
|
||||
func (c *jwtCacheFillerController) validateProviderDiscovery(ctx context.Context, spec *auth1alpha1.JWTAuthenticatorSpec, conditions []*metav1.Condition, prereqOk bool) (*providerJSON, *coreosoidc.Provider, []*metav1.Condition, error) {
|
||||
if !prereqOk {
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeDiscoveryValid,
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: reasonUnableToValidate,
|
||||
Message: msgUnableToValidate,
|
||||
})
|
||||
return nil, nil, conditions, nil
|
||||
}
|
||||
provider, err := coreosoidc.NewProvider(ctx, spec.Issuer)
|
||||
pJSON := &providerJSON{}
|
||||
if err != nil {
|
||||
errText := "could not perform oidc discovery on provider issuer"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeDiscoveryValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidDiscoveryProbe,
|
||||
Message: msg,
|
||||
})
|
||||
// resync err, may be machine or other types of non-config error
|
||||
return nil, nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
msg := "discovery performed successfully"
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeDiscoveryValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return pJSON, provider, conditions, nil
|
||||
}
|
||||
|
||||
func (c *jwtCacheFillerController) validateProviderJWKSURL(provider *coreosoidc.Provider, pJSON *providerJSON, conditions []*metav1.Condition, prereqOk bool) (string, []*metav1.Condition, error) {
|
||||
if provider == nil || pJSON == nil || !prereqOk {
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeJWKSURLValid,
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: reasonUnableToValidate,
|
||||
Message: msgUnableToValidate,
|
||||
})
|
||||
return "", conditions, nil
|
||||
}
|
||||
// should be impossible because coreosoidc.NewProvider validates this, thus we can't write a test to get in this state (currently)
|
||||
if err := provider.Claims(pJSON); err != nil {
|
||||
errText := "could not get provider jwks_uri"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeJWKSURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidProviderJWKSURL,
|
||||
Message: msg,
|
||||
})
|
||||
// resync err, the user may not be able to fix this via config, it may be the server may be misbehaving.
|
||||
return pJSON.JWKSURL, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
parsedJWKSURL, err := url.Parse(pJSON.JWKSURL)
|
||||
if err != nil {
|
||||
errText := "could not parse provider jwks_uri"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeJWKSURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidProviderJWKSURL,
|
||||
Message: msg,
|
||||
})
|
||||
// resync err, the user may not be able to fix this via config, it may be the server may be misbehaving.
|
||||
return pJSON.JWKSURL, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
// spec asserts https is required. https://openid.net/specs/openid-connect-discovery-1_0.html
|
||||
if parsedJWKSURL.Scheme != "https" {
|
||||
msg := fmt.Sprintf("jwks_uri %s has invalid scheme, require 'https'", pJSON.JWKSURL)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeJWKSURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidProviderJWKSURLScheme,
|
||||
Message: msg,
|
||||
})
|
||||
return pJSON.JWKSURL, conditions, fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("jwks_uri (%s) is a valid URL", parsedJWKSURL)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeJWKSURLValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return pJSON.JWKSURL, conditions, nil
|
||||
}
|
||||
|
||||
func (c *jwtCacheFillerController) validateTLS(spec *auth1alpha1.JWTAuthenticatorSpec, conditions []*metav1.Condition) (*x509.CertPool, []*metav1.Condition, bool) {
|
||||
rootCAs, _, err := pinnipedauthenticator.CABundle(spec.TLS)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: %s", "invalid TLS configuration", err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeTLSConfigurationValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidTLSConfiguration,
|
||||
Message: msg,
|
||||
})
|
||||
return rootCAs, conditions, false
|
||||
}
|
||||
|
||||
msg := "valid TLS configuration"
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeTLSConfigurationValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return rootCAs, conditions, true
|
||||
}
|
||||
|
||||
func (c *jwtCacheFillerController) validateIssuer(issuer string, conditions []*metav1.Condition) (*url.URL, []*metav1.Condition, bool) {
|
||||
issuerURL, err := url.Parse(issuer)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: %s", "spec.issuer URL is invalid", err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidIssuerURL,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
if issuerURL.Scheme != "https" {
|
||||
msg := fmt.Sprintf("spec.issuer %s has invalid scheme, require 'https'", issuer)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidIssuerURLScheme,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("spec.issuer (%s) is a valid URL", issuer)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return issuerURL, conditions, true
|
||||
}
|
||||
|
||||
func hadErrorCondition(conditions []*metav1.Condition) bool {
|
||||
for _, c := range conditions {
|
||||
if c.Status != metav1.ConditionTrue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package jwtcachefiller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -28,6 +31,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
pinnipedfake "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
|
||||
@@ -36,8 +40,8 @@ import (
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
)
|
||||
|
||||
@@ -61,18 +65,18 @@ func TestController(t *testing.T) {
|
||||
customGroupsClaim := "my-custom-groups-claim"
|
||||
distributedGroups := []string{"some-distributed-group-1", "some-distributed-group-2"}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
server := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
goodMux := http.NewServeMux()
|
||||
goodOIDCIssuerServer := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
mux.ServeHTTP(w, r)
|
||||
goodMux.ServeHTTP(w, r)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
mux.Handle("/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
goodMux.Handle("/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s"}`, server.URL, server.URL+"/jwks.json")
|
||||
_, err := fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s"}`, goodOIDCIssuerServer.URL, goodOIDCIssuerServer.URL+"/jwks.json")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
mux.Handle("/jwks.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
goodMux.Handle("/jwks.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ecJWK := jose.JSONWebKey{
|
||||
Key: goodECSigningKey,
|
||||
KeyID: goodECSigningKeyID,
|
||||
@@ -90,19 +94,20 @@ func TestController(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, json.NewEncoder(w).Encode(jwks))
|
||||
}))
|
||||
|
||||
// Claims without the subject, to be used distributed claims tests.
|
||||
// OIDC 1.0 section 5.6.2:
|
||||
// A sub (subject) Claim SHOULD NOT be returned from the Claims Provider unless its value
|
||||
// is an identifier for the End-User at the Claims Provider (and not for the OpenID Provider or another party);
|
||||
// this typically means that a sub Claim SHOULD NOT be provided.
|
||||
claimsWithoutSubject := jwt.Claims{
|
||||
Issuer: server.URL,
|
||||
Issuer: goodOIDCIssuerServer.URL,
|
||||
Audience: []string{goodAudience},
|
||||
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
||||
}
|
||||
mux.Handle("/claim_source", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
goodMux.Handle("/claim_source", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Unfortunately we have to set this up pretty early in the test because we can't redeclare
|
||||
// mux.Handle. This means that we can't return a different groups claim per test; we have to
|
||||
// return both and predecide which groups are returned.
|
||||
@@ -123,7 +128,7 @@ func TestController(t *testing.T) {
|
||||
_, err = w.Write([]byte(distributedClaimsJwt))
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
mux.Handle("/wrong_claim_source", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
goodMux.Handle("/wrong_claim_source", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Unfortunately we have to set this up pretty early in the test because we can't redeclare
|
||||
// mux.Handle. This means that we can't return a different groups claim per test; we have to
|
||||
// return both and predecide which groups are returned.
|
||||
@@ -144,17 +149,48 @@ func TestController(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
goodIssuer := server.URL
|
||||
badMuxInvalidJWKSURI := http.NewServeMux()
|
||||
badOIDCIssuerServerInvalidJWKSURI := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
badMuxInvalidJWKSURI.ServeHTTP(w, r)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
badMuxInvalidJWKSURI.Handle("/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s"}`, badOIDCIssuerServerInvalidJWKSURI.URL, "https://.café .com/café/café/café/coffee/jwks.json")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
badMuxInvalidJWKSURIScheme := http.NewServeMux()
|
||||
badOIDCIssuerServerInvalidJWKSURIScheme := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
badMuxInvalidJWKSURIScheme.ServeHTTP(w, r)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
badMuxInvalidJWKSURIScheme.Handle("/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s"}`, badOIDCIssuerServerInvalidJWKSURIScheme.URL, "http://.café.com/café/café/café/coffee/jwks.json")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
goodIssuer := goodOIDCIssuerServer.URL
|
||||
badIssuerInvalidJWKSURI := badOIDCIssuerServerInvalidJWKSURI.URL
|
||||
badIssuerInvalidJWKSURIScheme := badOIDCIssuerServerInvalidJWKSURIScheme.URL
|
||||
someOtherIssuer := "https://some-other-issuer.com" // placeholder only for tests that don't get far enough to make requests
|
||||
someOtherLocalhostIssuer := "https://127.0.0.1:443/some-other-issuer"
|
||||
someOtherLocalhostIssuerParsed, err := url.Parse(someOtherLocalhostIssuer)
|
||||
require.NoError(t, err)
|
||||
|
||||
frozenNow := time.Date(2020, time.September, 23, 7, 42, 0, 0, time.Local)
|
||||
frozenMetav1Now := metav1.NewTime(frozenNow)
|
||||
|
||||
someJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(server.TLS),
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
someJWTAuthenticatorSpecWithUsernameClaim := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(server.TLS),
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
Claims: auth1alpha1.JWTTokenClaims{
|
||||
Username: "my-custom-username-claim",
|
||||
},
|
||||
@@ -162,48 +198,338 @@ func TestController(t *testing.T) {
|
||||
someJWTAuthenticatorSpecWithGroupsClaim := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(server.TLS),
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
Claims: auth1alpha1.JWTTokenClaims{
|
||||
Groups: customGroupsClaim,
|
||||
},
|
||||
}
|
||||
otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://some-other-issuer.com",
|
||||
Issuer: someOtherIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
||||
// Some random generated cert
|
||||
// Issuer: C=US, O=Pivotal
|
||||
// No SAN provided
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
||||
}
|
||||
missingTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
}
|
||||
invalidTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://some-other-issuer.com",
|
||||
Issuer: someOtherIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid base64-encoded data"},
|
||||
}
|
||||
|
||||
invalidIssuerJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://.café .com/café/café/café/coffee",
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
invalidIssuerSchemeJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "http://.café.com/café/café/café/coffee",
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
|
||||
validIssuerURLButDoesNotExistJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: someOtherLocalhostIssuer,
|
||||
Audience: goodAudience,
|
||||
}
|
||||
badIssuerJWKSURIJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: badIssuerInvalidJWKSURI,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURI.TLS),
|
||||
}
|
||||
badIssuerJWKSURISchemeJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: badIssuerInvalidJWKSURIScheme,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURIScheme.TLS),
|
||||
}
|
||||
|
||||
happyReadyCondition := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: "the JWTAuthenticator is ready",
|
||||
}
|
||||
}
|
||||
sadReadyCondition := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "NotReady",
|
||||
Message: "the JWTAuthenticator is not ready: see other conditions for details",
|
||||
}
|
||||
}
|
||||
|
||||
happyTLSConfigurationValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "TLSConfigurationValid",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: "valid TLS configuration",
|
||||
}
|
||||
}
|
||||
sadTLSConfigurationValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "TLSConfigurationValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidTLSConfiguration",
|
||||
Message: "invalid TLS configuration: illegal base64 data at input byte 7",
|
||||
}
|
||||
}
|
||||
|
||||
happyIssuerURLValid := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: fmt.Sprintf("spec.issuer (%s) is a valid URL", issuer),
|
||||
}
|
||||
}
|
||||
sadIssuerURLValidInvalid := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidIssuerURL",
|
||||
Message: fmt.Sprintf(`spec.issuer URL is invalid: parse "%s": invalid character " " in host name`, issuer),
|
||||
}
|
||||
}
|
||||
|
||||
sadIssuerURLValidInvalidScheme := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidIssuerURLScheme",
|
||||
Message: fmt.Sprintf("spec.issuer %s has invalid scheme, require 'https'", issuer),
|
||||
}
|
||||
}
|
||||
|
||||
happyAuthenticatorValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: "authenticator initialized",
|
||||
}
|
||||
}
|
||||
unknownAuthenticatorValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "Unknown",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; other issues present",
|
||||
}
|
||||
}
|
||||
// NOTE: we can't reach this error the way our code is written.
|
||||
// We check many things and fail early, resulting in an "Unknown" Authenticator status.
|
||||
// The only possible fail for the Authenticator itself would require us to allow more
|
||||
// configuration for users. See comments in the jwtauthenticator.go newCachedJWTAuthenticator()
|
||||
// func itself for more information.
|
||||
// sadAuthenticatorValid := func() metav1.Condition {}
|
||||
|
||||
happyDiscoveryURLValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "DiscoveryURLValid",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: "discovery performed successfully",
|
||||
}
|
||||
}
|
||||
unknownDiscoveryURLValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "DiscoveryURLValid",
|
||||
Status: "Unknown",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; other issues present",
|
||||
}
|
||||
}
|
||||
sadDiscoveryURLValidx509 := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "DiscoveryURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidDiscoveryProbe",
|
||||
Message: fmt.Sprintf(`could not perform oidc discovery on provider issuer: Get "%s/.well-known/openid-configuration": tls: failed to verify certificate: x509: certificate signed by unknown authority`, issuer),
|
||||
}
|
||||
}
|
||||
sadDiscoveryURLValidConnectionRefused := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
parsed, err := url.Parse(issuer)
|
||||
require.NoError(t, err)
|
||||
return metav1.Condition{
|
||||
Type: "DiscoveryURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidDiscoveryProbe",
|
||||
Message: fmt.Sprintf(`could not perform oidc discovery on provider issuer: Get "%s/.well-known/openid-configuration": dial tcp %s: connect: connection refused`, issuer, parsed.Host),
|
||||
}
|
||||
}
|
||||
|
||||
happyJWKSURLValid := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
parsed, err := url.Parse(issuer)
|
||||
require.NoError(t, err)
|
||||
return metav1.Condition{
|
||||
Type: "JWKSURLValid",
|
||||
Status: "True",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "Success",
|
||||
Message: fmt.Sprintf("jwks_uri (https://%s/jwks.json) is a valid URL", parsed.Host),
|
||||
}
|
||||
}
|
||||
unknownJWKSURLValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "JWKSURLValid",
|
||||
Status: "Unknown",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; other issues present",
|
||||
}
|
||||
}
|
||||
sadJWKSURLValidParseURI := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "JWKSURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidProviderJWKSURL",
|
||||
Message: `could not parse provider jwks_uri: parse "` + issuer + `": invalid character " " in host name`,
|
||||
}
|
||||
}
|
||||
sadJWKSURLValidScheme := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "JWKSURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidProviderJWKSURLScheme",
|
||||
Message: `jwks_uri ` + issuer + ` has invalid scheme, require 'https'`,
|
||||
}
|
||||
}
|
||||
|
||||
// happyJWKSURLValidResponse := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
// return metav1.Condition{
|
||||
// Type: "JWKSURLValidResponse",
|
||||
// Status: "True",
|
||||
// ObservedGeneration: observedGeneration,
|
||||
// LastTransitionTime: time,
|
||||
// Reason: "Success",
|
||||
// Message: "jwks_uri responds to requests",
|
||||
// }
|
||||
// }
|
||||
// sadJWKSURLValidResponse := func() metav1.Condition {}
|
||||
|
||||
// TODO: extract to a helper since copied from FederationDomain
|
||||
sortConditionsByType := func(c []metav1.Condition) []metav1.Condition {
|
||||
cp := make([]metav1.Condition, len(c))
|
||||
copy(cp, c)
|
||||
sort.SliceStable(cp, func(i, j int) bool {
|
||||
return cp[i].Type < cp[j].Type
|
||||
})
|
||||
return cp
|
||||
}
|
||||
|
||||
replaceConditions := func(conditions []metav1.Condition, sadConditions []metav1.Condition) []metav1.Condition {
|
||||
for _, sadReplaceCondition := range sadConditions {
|
||||
for origIndex, origCondition := range conditions {
|
||||
if origCondition.Type == sadReplaceCondition.Type {
|
||||
conditions[origIndex] = sadReplaceCondition
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
|
||||
// condition collections
|
||||
allHappyConditionsSuccess := func(issuer string, time metav1.Time, observedGeneration int64) []metav1.Condition {
|
||||
return sortConditionsByType([]metav1.Condition{
|
||||
happyAuthenticatorValid(time, observedGeneration),
|
||||
happyDiscoveryURLValid(time, observedGeneration),
|
||||
happyIssuerURLValid(issuer, time, observedGeneration),
|
||||
happyJWKSURLValid(issuer, time, observedGeneration),
|
||||
// happyJWKSURLValidResponse(time, observedGeneration),
|
||||
happyReadyCondition(time, observedGeneration),
|
||||
happyTLSConfigurationValid(time, observedGeneration),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(BEN): might need a full JWTAuthenticatorStatus with Phase, otherwise remove this.
|
||||
// someJWTAuthenticatorStatus := auth1alpha1.JWTAuthenticatorStatus{
|
||||
// Conditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
// Phase: auth1alpha1.JWTAuthenticatorPhaseReady,
|
||||
// }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cache func(*testing.T, *authncache.Cache, bool)
|
||||
syncKey controllerlib.Key
|
||||
jwtAuthenticators []runtime.Object
|
||||
wantClose bool
|
||||
wantErr testutil.RequireErrorStringFunc
|
||||
wantLogs []string
|
||||
name string
|
||||
cache func(*testing.T, *authncache.Cache, bool)
|
||||
// syncKey is used behind the scenes to simulate the pre-existence of jwtAuthenticator(s).
|
||||
// if not provided, then the test assumes the jwtAuthenticator(s) are new Authenticator(s).
|
||||
syncKey controllerlib.Key
|
||||
jwtAuthenticators []runtime.Object
|
||||
wantClose bool
|
||||
// Only errors that are non-config related errors are returned from the sync loop.
|
||||
// Errors such as url.Parse of the issuer are not returned as they imply a user error.
|
||||
// Since these errors trigger a resync, we are careful only to return an error when
|
||||
// something can be automatically corrected on a retry (ie an error that might be networking).
|
||||
wantSyncLoopErr testutil.RequireErrorStringFunc
|
||||
wantLogs []map[string]any
|
||||
wantStatusConditions []metav1.Condition
|
||||
wantStatusPhase auth1alpha1.JWTAuthenticatorPhase
|
||||
wantCacheEntries int
|
||||
wantUsernameClaim string
|
||||
wantGroupsClaim string
|
||||
runTestsOnResultingAuthenticator bool
|
||||
}{
|
||||
{
|
||||
name: "not found",
|
||||
name: "404: jwt authenticator not found will abort sync loop and not attempt to write status",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="Sync() found that the JWTAuthenticator does not exist yet or was deleted"`,
|
||||
// timestamp and caller will not be empty, but we aren't concerned with the values. only testing the interesting bits.
|
||||
wantLogs: []map[string]any{
|
||||
{
|
||||
"level": "info",
|
||||
"timestamp": frozenNow.String(),
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "Sync() found that the JWTAuthenticator does not exist yet or was deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// TODO:
|
||||
// lines ~ 146
|
||||
// should we test this? can we simulate a server error without rewriting the whole test?
|
||||
// prob with a if(tt.serverErr) then controller := New( withBadClients ) or something....
|
||||
// name: "non-404 `failed to get JWTAuthenticator` for other API server reasons",
|
||||
// },
|
||||
{
|
||||
name: "valid jwt authenticator with CA",
|
||||
name: "valid jwt authenticator with CA will complete sync loop successfully with success conditions and ready phase",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
@@ -213,14 +539,22 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "added new jwt authenticator",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
wantStatusConditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
runTestsOnResultingAuthenticator: true,
|
||||
},
|
||||
{
|
||||
name: "valid jwt authenticator with custom username claim",
|
||||
name: "valid jwt authenticator with custom username claim will complete sync loop successfully with success conditions and ready phase",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
@@ -230,15 +564,23 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpecWithUsernameClaim,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "added new jwt authenticator",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
wantStatusConditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
wantUsernameClaim: someJWTAuthenticatorSpecWithUsernameClaim.Claims.Username,
|
||||
runTestsOnResultingAuthenticator: true,
|
||||
},
|
||||
{
|
||||
name: "valid jwt authenticator with custom groups claim",
|
||||
name: "valid jwt authenticator with custom groups claim will complete sync loop successfully with success conditions and ready phase",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
@@ -248,15 +590,23 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpecWithGroupsClaim,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "added new jwt authenticator",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
wantStatusConditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
wantGroupsClaim: someJWTAuthenticatorSpecWithGroupsClaim.Claims.Groups,
|
||||
runTestsOnResultingAuthenticator: true,
|
||||
},
|
||||
{
|
||||
name: "updating jwt authenticator with new fields closes previous instance",
|
||||
name: "updating jwt authenticator with new fields closes previous instance and will complete sync loop successfully with success conditions and ready phase",
|
||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||
cache.Store(
|
||||
authncache.Key{
|
||||
@@ -277,14 +627,22 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "added new jwt authenticator",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
wantStatusConditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
runTestsOnResultingAuthenticator: true,
|
||||
},
|
||||
{
|
||||
name: "updating jwt authenticator with the same value does nothing",
|
||||
name: "updating jwt authenticator with the same value does not trigger sync loop and will not update conditions or phase",
|
||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||
cache.Store(
|
||||
authncache.Key{
|
||||
@@ -305,14 +663,24 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="actual jwt authenticator and desired jwt authenticator are the same" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "actual jwt authenticator and desired jwt authenticator are the same",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
// We do not cache status, so no status or phase is expected on an updated jwt authenticator
|
||||
// if the cached spec matches, the sync loop will exit before updating status
|
||||
// wantStatusConditions: nil
|
||||
// wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
runTestsOnResultingAuthenticator: false, // skip the tests because the authenticator left in the cache is the mock version that was added above
|
||||
},
|
||||
{
|
||||
name: "updating jwt authenticator when cache value is wrong type",
|
||||
name: "updating jwt authenticator when cache value is wrong type will complete sync loop successfully with success conditions and ready phase",
|
||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||
cache.Store(
|
||||
authncache.Key{
|
||||
@@ -332,15 +700,27 @@ func TestController(t *testing.T) {
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="wrong JWT authenticator type in cache" "actualType"="struct { authenticator.Token }"`,
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantLogs: []map[string]any{{
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "wrong JWT authenticator type in cache",
|
||||
"actualType": "struct { authenticator.Token }",
|
||||
}, {
|
||||
"level": "info",
|
||||
"logger": "jwtcachefiller-controller",
|
||||
"message": "added new jwt authenticator",
|
||||
"issuer": goodIssuer,
|
||||
"jwtAuthenticator": map[string]interface{}{
|
||||
"name": "test-name",
|
||||
},
|
||||
}},
|
||||
wantStatusConditions: allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
wantStatusPhase: "Ready",
|
||||
wantCacheEntries: 1,
|
||||
runTestsOnResultingAuthenticator: true,
|
||||
},
|
||||
{
|
||||
name: "valid jwt authenticator without CA",
|
||||
name: "TLS: valid jwt authenticator without CA will fail to cache the authenticator and will fail with failed and unknown conditions and Error phase and will enqueue a resync in case of machine error",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
@@ -350,10 +730,23 @@ func TestController(t *testing.T) {
|
||||
Spec: *missingTLSJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantErr: testutil.WantX509UntrustedCertErrorString(`failed to build jwt authenticator: could not initialize provider: Get "`+goodIssuer+`/.well-known/openid-configuration": %s`, "Acme Co"),
|
||||
// no explicit logs, this is an issue of config, the user must provide TLS config for the
|
||||
// custom cert provided for this server.
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadDiscoveryURLValidx509(goodIssuer, frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
wantSyncLoopErr: testutil.WantX509UntrustedCertErrorString(`could not perform oidc discovery on provider issuer: Get "`+goodIssuer+`/.well-known/openid-configuration": %s`, "Acme Co"),
|
||||
wantCacheEntries: 0,
|
||||
},
|
||||
{
|
||||
name: "invalid jwt authenticator CA",
|
||||
name: "validateTLS: invalid jwt authenticator CA will fail sync loop and will report failed and unknown conditions and Error phase, but will not enqueue a resync due to user config error",
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
@@ -363,8 +756,143 @@ func TestController(t *testing.T) {
|
||||
Spec: *invalidTLSJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantErr: testutil.WantExactErrorString("failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7"),
|
||||
// no explicit logs, this is an issue of config, the user must provide TLS config that
|
||||
// isn't incorrect due to mistyping or other issues
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(someOtherIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadTLSConfigurationValid(frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
wantCacheEntries: 0,
|
||||
}, {
|
||||
name: "validateIssuer: parsing error (spec.issuer URL is invalid) will fail sync loop and will report failed and unknown conditions and Error phase, but will not enqueue a resync due to user config error",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: *invalidIssuerJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadIssuerURLValidInvalid("https://.café .com/café/café/café/coffee", frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
}, {
|
||||
name: "validateIssuer: parsing error (spec.issuer URL has invalid scheme, requires https) will fail sync loop and will report failed and unknown conditions and Error phase, but will not enqueue a resync due to user config error",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: *invalidIssuerSchemeJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadIssuerURLValidInvalidScheme("http://.café.com/café/café/café/coffee", frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
}, {
|
||||
name: "validateProviderDiscovery: could not perform oidc discovery on provider issuer will fail sync loop and will report failed and unknown conditions and Error phase and will enqueue new sync",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: *validIssuerURLButDoesNotExistJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
happyIssuerURLValid(someOtherLocalhostIssuer, frozenMetav1Now, 0),
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadDiscoveryURLValidConnectionRefused(someOtherLocalhostIssuer, frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
wantSyncLoopErr: testutil.WantExactErrorString(`could not perform oidc discovery on provider issuer: Get "` + someOtherLocalhostIssuer + `/.well-known/openid-configuration": dial tcp ` + someOtherLocalhostIssuerParsed.Host + `: connect: connection refused`),
|
||||
},
|
||||
// cannot be tested currently the way the coreos lib works.
|
||||
// the constructor requires an issuer in the payload and validates the issuer matches the actual issuer,
|
||||
// which ensures the .Claims() parsing cannot fail (in the current impl)
|
||||
// { name: "validateProviderJWKSURL: could not get provider jwks_uri... ",},
|
||||
{
|
||||
name: "validateProviderJWKSURL: could not parse provider jwks_uri will fail sync loop and will report failed and unknown conditions and Error phase and will enqueue new sync",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: *badIssuerJWKSURIJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
happyIssuerURLValid(badIssuerInvalidJWKSURI, frozenMetav1Now, 0),
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
sadJWKSURLValidParseURI("https://.café .com/café/café/café/coffee/jwks.json", frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
wantSyncLoopErr: testutil.WantExactErrorString(`could not parse provider jwks_uri: parse "https://.café .com/café/café/café/coffee/jwks.json": invalid character " " in host name`),
|
||||
}, {
|
||||
name: "validateProviderJWKSURL: invalid scheme, requires 'https' will fail sync loop and will report failed and unknown conditions and Error phase and will enqueue new sync",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: *badIssuerJWKSURISchemeJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantStatusConditions: replaceConditions(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
happyIssuerURLValid(badIssuerInvalidJWKSURIScheme, frozenMetav1Now, 0),
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
sadJWKSURLValidScheme("http://.café.com/café/café/café/coffee/jwks.json", frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
wantStatusPhase: "Error",
|
||||
wantSyncLoopErr: testutil.WantExactErrorString("jwks_uri http://.café.com/café/café/café/coffee/jwks.json has invalid scheme, require 'https'"),
|
||||
},
|
||||
// cannot be tested the way we are invoking oidc.New as we don't provide enough configuration
|
||||
// knobs to actually invoke the code in a broken way. We always give a good client, good keys, and
|
||||
// good signing algos. In the future if we allow any of these to be configured we may have opportunity
|
||||
// to test for errors.
|
||||
// {name: "newCachedJWTAuthenticator: could not initialize oidc authenticator..." },
|
||||
// TODO: are happy, unknown, sad covered in all the above? or do we need more?
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -372,32 +900,75 @@ func TestController(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fakeClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...)
|
||||
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
|
||||
pinnipedAPIClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...)
|
||||
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(pinnipedAPIClient, 0)
|
||||
cache := authncache.New()
|
||||
testLog := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
|
||||
|
||||
var log bytes.Buffer
|
||||
logger := plog.TestLogger(t, &log)
|
||||
|
||||
if tt.cache != nil {
|
||||
tt.cache(t, cache, tt.wantClose)
|
||||
}
|
||||
|
||||
controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog.Logger)
|
||||
controller := New(
|
||||
cache,
|
||||
pinnipedAPIClient,
|
||||
pinnipedInformers.Authentication().V1alpha1().JWTAuthenticators(),
|
||||
clocktesting.NewFakeClock(frozenNow),
|
||||
logger)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
informers.Start(ctx.Done())
|
||||
pinnipedInformers.Start(ctx.Done())
|
||||
controllerlib.TestRunSynchronously(t, controller)
|
||||
|
||||
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
|
||||
|
||||
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != nil {
|
||||
testutil.RequireErrorStringFromErr(t, err, tt.wantErr)
|
||||
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantSyncLoopErr != nil {
|
||||
testutil.RequireErrorStringFromErr(t, err, tt.wantSyncLoopErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tt.wantLogs, testLog.Lines())
|
||||
require.Equal(t, tt.wantCacheEntries, len(cache.Keys()))
|
||||
|
||||
actualLogLines := logLines(log.String())
|
||||
require.Equal(t, len(actualLogLines), len(tt.wantLogs), "log line count should be correct")
|
||||
|
||||
for logLineNum, logLine := range actualLogLines {
|
||||
require.NotNil(t, tt.wantLogs[logLineNum], "expected log line should never be empty")
|
||||
var lineStruct map[string]any
|
||||
err := json.Unmarshal([]byte(logLine), &lineStruct)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.Equal(t, lineStruct["level"], tt.wantLogs[logLineNum]["level"], fmt.Sprintf("log line (%d) log level should be correct", logLineNum))
|
||||
// require.Equal(t, lineStruct["timestamp"], tt.wantLogs[logLineNum]["timestamp"], fmt.Sprintf("log line (%d) timestamp should be correct", logLineNum))
|
||||
require.Equal(t, lineStruct["logger"], tt.wantLogs[logLineNum]["logger"], fmt.Sprintf("log line (%d) logger should be correct", logLineNum))
|
||||
require.NotEmpty(t, lineStruct["caller"], fmt.Sprintf("log line (%d) caller should not be empty", logLineNum))
|
||||
require.Equal(t, lineStruct["message"], tt.wantLogs[logLineNum]["message"], fmt.Sprintf("log line (%d) message should be correct", logLineNum))
|
||||
if lineStruct["issuer"] != nil {
|
||||
require.Equal(t, lineStruct["issuer"], tt.wantLogs[logLineNum]["issuer"], fmt.Sprintf("log line (%d) issuer should be correct", logLineNum))
|
||||
}
|
||||
if lineStruct["jwtAuthenticator"] != nil {
|
||||
require.Equal(t, lineStruct["jwtAuthenticator"], tt.wantLogs[logLineNum]["jwtAuthenticator"], fmt.Sprintf("log line (%d) jwtAuthenticator should be correct", logLineNum))
|
||||
}
|
||||
if lineStruct["actualType"] != nil {
|
||||
require.Equal(t, lineStruct["actualType"], tt.wantLogs[logLineNum]["actualType"], fmt.Sprintf("log line (%d) actualType should be correct", logLineNum))
|
||||
}
|
||||
}
|
||||
|
||||
if tt.jwtAuthenticators != nil {
|
||||
var jwtAuthSubject *auth1alpha1.JWTAuthenticator
|
||||
getCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
jwtAuthSubject, getErr := pinnipedAPIClient.AuthenticationV1alpha1().JWTAuthenticators().Get(getCtx, "test-name", metav1.GetOptions{})
|
||||
require.NoError(t, getErr)
|
||||
require.Equal(t, tt.wantStatusConditions, jwtAuthSubject.Status.Conditions, "status.conditions must be correct")
|
||||
require.Equal(t, tt.wantStatusPhase, jwtAuthSubject.Status.Phase, "jwt authenticator status.phase should be correct")
|
||||
}
|
||||
|
||||
require.Equal(t, tt.wantCacheEntries, len(cache.Keys()), fmt.Sprintf("expected cache entries is incorrect. wanted:%d, got: %d, keys: %v", tt.wantCacheEntries, len(cache.Keys()), cache.Keys()))
|
||||
|
||||
if !tt.runTestsOnResultingAuthenticator {
|
||||
return // end of test unless we wanted to run tests on the resulting authenticator from the cache
|
||||
@@ -413,7 +984,7 @@ func TestController(t *testing.T) {
|
||||
require.NotNil(t, cachedAuthenticator)
|
||||
|
||||
// Schedule it to be closed at the end of the test.
|
||||
t.Cleanup(cachedAuthenticator.(*jwtAuthenticator).Close)
|
||||
t.Cleanup(cachedAuthenticator.(*cachedJWTAuthenticator).Close)
|
||||
|
||||
const (
|
||||
goodSubject = "some-subject"
|
||||
@@ -772,8 +1343,16 @@ func newCacheValue(t *testing.T, spec auth1alpha1.JWTAuthenticatorSpec, wantClos
|
||||
}
|
||||
tokenAuthenticatorCloser.EXPECT().Close().Times(wantCloses)
|
||||
|
||||
return &jwtAuthenticator{
|
||||
return &cachedJWTAuthenticator{
|
||||
tokenAuthenticatorCloser: tokenAuthenticatorCloser,
|
||||
spec: &spec,
|
||||
}
|
||||
}
|
||||
|
||||
func logLines(logs string) []string {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(logs), "\n")
|
||||
}
|
||||
|
||||
@@ -236,6 +236,8 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
|
||||
WithController(
|
||||
webhookcachefiller.New(
|
||||
c.AuthenticatorCache,
|
||||
// TODO (BEN): add the client here for next story
|
||||
// client.PinnipedConcierge.AuthenticationV1alpha1().WebhookAuthenticators(),
|
||||
informers.pinniped.Authentication().V1alpha1().WebhookAuthenticators(),
|
||||
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
|
||||
),
|
||||
@@ -244,8 +246,10 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
|
||||
WithController(
|
||||
jwtcachefiller.New(
|
||||
c.AuthenticatorCache,
|
||||
client.PinnipedConcierge,
|
||||
informers.pinniped.Authentication().V1alpha1().JWTAuthenticators(),
|
||||
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
|
||||
clock.RealClock{},
|
||||
plog.New(),
|
||||
),
|
||||
singletonWorker,
|
||||
).
|
||||
|
||||
Reference in New Issue
Block a user