From 946b0539d2929a34e94d9783da3c354015f0ea07 Mon Sep 17 00:00:00 2001 From: Andrew Keesler Date: Mon, 7 Dec 2020 20:37:43 -0500 Subject: [PATCH 1/6] Add JWTAuthenticator API type Signed-off-by: Andrew Keesler --- .../authentication/v1alpha1/register.go.tmpl | 2 + .../authentication/v1alpha1/types_jwt.go.tmpl | 62 ++++++ ...cierge.pinniped.dev_jwtauthenticators.yaml | 155 +++++++++++++++ deploy/concierge/z0_crd_overlay.yaml | 6 + generated/1.17/README.adoc | 83 +++++++- .../authentication/v1alpha1/register.go | 2 + .../authentication/v1alpha1/types_jwt.go | 62 ++++++ .../v1alpha1/zz_generated.deepcopy.go | 105 ++++++++++ .../v1alpha1/authentication_client.go | 5 + .../fake/fake_authentication_client.go | 4 + .../v1alpha1/fake/fake_jwtauthenticator.go | 127 ++++++++++++ .../v1alpha1/generated_expansion.go | 2 + .../v1alpha1/jwtauthenticator.go | 178 +++++++++++++++++ .../authentication/v1alpha1/interface.go | 7 + .../v1alpha1/jwtauthenticator.go | 76 ++++++++ .../informers/externalversions/generic.go | 2 + .../v1alpha1/expansion_generated.go | 8 + .../v1alpha1/jwtauthenticator.go | 81 ++++++++ ...cierge.pinniped.dev_jwtauthenticators.yaml | 155 +++++++++++++++ generated/1.18/README.adoc | 83 +++++++- .../authentication/v1alpha1/register.go | 2 + .../authentication/v1alpha1/types_jwt.go | 62 ++++++ .../v1alpha1/zz_generated.deepcopy.go | 105 ++++++++++ .../v1alpha1/authentication_client.go | 5 + .../fake/fake_authentication_client.go | 4 + .../v1alpha1/fake/fake_jwtauthenticator.go | 129 +++++++++++++ .../v1alpha1/generated_expansion.go | 2 + .../v1alpha1/jwtauthenticator.go | 182 ++++++++++++++++++ .../authentication/v1alpha1/interface.go | 7 + .../v1alpha1/jwtauthenticator.go | 77 ++++++++ .../informers/externalversions/generic.go | 2 + .../v1alpha1/expansion_generated.go | 8 + .../v1alpha1/jwtauthenticator.go | 81 ++++++++ ...cierge.pinniped.dev_jwtauthenticators.yaml | 155 +++++++++++++++ generated/1.19/README.adoc | 83 +++++++- .../authentication/v1alpha1/register.go | 2 + .../authentication/v1alpha1/types_jwt.go | 62 ++++++ .../v1alpha1/zz_generated.deepcopy.go | 105 ++++++++++ .../v1alpha1/authentication_client.go | 5 + .../fake/fake_authentication_client.go | 4 + .../v1alpha1/fake/fake_jwtauthenticator.go | 129 +++++++++++++ .../v1alpha1/generated_expansion.go | 2 + .../v1alpha1/jwtauthenticator.go | 182 ++++++++++++++++++ .../authentication/v1alpha1/interface.go | 7 + .../v1alpha1/jwtauthenticator.go | 77 ++++++++ .../informers/externalversions/generic.go | 2 + .../v1alpha1/expansion_generated.go | 8 + .../v1alpha1/jwtauthenticator.go | 86 +++++++++ ...cierge.pinniped.dev_jwtauthenticators.yaml | 155 +++++++++++++++ test/integration/kube_api_discovery_test.go | 8 + 50 files changed, 2928 insertions(+), 15 deletions(-) create mode 100644 apis/concierge/authentication/v1alpha1/types_jwt.go.tmpl create mode 100644 deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml create mode 100644 generated/1.17/apis/concierge/authentication/v1alpha1/types_jwt.go create mode 100644 generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go create mode 100644 generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.17/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.17/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml create mode 100644 generated/1.18/apis/concierge/authentication/v1alpha1/types_jwt.go create mode 100644 generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go create mode 100644 generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.18/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.18/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml create mode 100644 generated/1.19/apis/concierge/authentication/v1alpha1/types_jwt.go create mode 100644 generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go create mode 100644 generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.19/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go create mode 100644 generated/1.19/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml diff --git a/apis/concierge/authentication/v1alpha1/register.go.tmpl b/apis/concierge/authentication/v1alpha1/register.go.tmpl index 413aa5f6..a00b5e9e 100644 --- a/apis/concierge/authentication/v1alpha1/register.go.tmpl +++ b/apis/concierge/authentication/v1alpha1/register.go.tmpl @@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &WebhookAuthenticator{}, &WebhookAuthenticatorList{}, + &JWTAuthenticator{}, + &JWTAuthenticatorList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/apis/concierge/authentication/v1alpha1/types_jwt.go.tmpl b/apis/concierge/authentication/v1alpha1/types_jwt.go.tmpl new file mode 100644 index 00000000..5d0604bd --- /dev/null +++ b/apis/concierge/authentication/v1alpha1/types_jwt.go.tmpl @@ -0,0 +1,62 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// Status of a JWT authenticator. +type JWTAuthenticatorStatus struct { + // Represents the observations of the authenticator's current state. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// Spec for configuring a JWT authenticator. +type JWTAuthenticatorSpec struct { + // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // also used to validate the "iss" JWT claim. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^https://` + Issuer string `json:"issuer"` + + // Audience is the required value of the "aud" JWT claim. + // +kubebuilder:validation:MinLength=1 + Audience string `json:"audience"` + + // TLS configuration for communicating with the OIDC provider. + // +optional + TLS *TLSSpec `json:"tls,omitempty"` +} + +// JWTAuthenticator describes the configuration of a JWT authenticator. +// +// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid +// signature, existence of claims, etc.) and extract the username and groups from the token. +// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators +// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` +type JWTAuthenticator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec for configuring the authenticator. + Spec JWTAuthenticatorSpec `json:"spec"` + + // Status of the authenticator. + Status JWTAuthenticatorStatus `json:"status,omitempty"` +} + +// List of JWTAuthenticator objects. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type JWTAuthenticatorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []JWTAuthenticator `json:"items"` +} diff --git a/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml new file mode 100644 index 00000000..ed16bf0c --- /dev/null +++ b/deploy/concierge/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -0,0 +1,155 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: jwtauthenticators.authentication.concierge.pinniped.dev +spec: + group: authentication.concierge.pinniped.dev + names: + categories: + - pinniped + - pinniped-authenticator + - pinniped-authenticators + kind: JWTAuthenticator + listKind: JWTAuthenticatorList + plural: jwtauthenticators + singular: jwtauthenticator + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.issuer + name: Issuer + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "JWTAuthenticator describes the configuration of a JWT authenticator. + \n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation + on it (e.g., valid signature, existence of claims, etc.) and extract the + username and groups from the token." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec for configuring the authenticator. + properties: + audience: + description: Audience is the required value of the "aud" JWT claim. + minLength: 1 + type: string + issuer: + description: Issuer is the OIDC issuer URL that will be used to discover + public signing keys. Issuer is also used to validate the "iss" JWT + claim. + minLength: 1 + pattern: ^https:// + type: string + tls: + description: TLS configuration for communicating with the OIDC provider. + properties: + certificateAuthorityData: + description: X.509 Certificate Authority (base64-encoded PEM bundle). + If omitted, a default set of system roots will be trusted. + type: string + type: object + required: + - audience + - issuer + type: object + status: + description: Status of the authenticator. + properties: + conditions: + description: Represents the observations of the authenticator's current + state. + items: + description: Condition status of a resource (mirrored from the metav1.Condition + type added in Kubernetes 1.19). In a future API version we can + switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/deploy/concierge/z0_crd_overlay.yaml b/deploy/concierge/z0_crd_overlay.yaml index a0e06838..011d69d6 100644 --- a/deploy/concierge/z0_crd_overlay.yaml +++ b/deploy/concierge/z0_crd_overlay.yaml @@ -15,3 +15,9 @@ metadata: metadata: #@overlay/match missing_ok=True labels: #@ labels() + +#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"jwtauthenticators.authentication.concierge.pinniped.dev"}}), expects=1 +--- +metadata: + #@overlay/match missing_ok=True + labels: #@ labels() diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index a81aa6e9..0ccdd1df 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -22,10 +22,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio [id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition"] ==== Condition -Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$] **** @@ -41,13 +42,85 @@ Condition status of a resource (mirrored from the metav1.Condition type added in |=== -[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec"] -==== TLSSpec +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-conditionstatus"] +==== ConditionStatus (string) + -Configuration for configuring TLS on various authenticators. .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator"] +==== JWTAuthenticator + +JWTAuthenticator describes the configuration of a JWT authenticator. + Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid signature, existence of claims, etc.) and extract the username and groups from the token. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorlist[$$JWTAuthenticatorList$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. +|=== + + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] +==== JWTAuthenticatorSpec + +Spec for configuring a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. +| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] +==== JWTAuthenticatorStatus + +Status of a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec"] +==== TLSSpec + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec[$$WebhookAuthenticatorSpec$$] **** @@ -111,7 +184,7 @@ Status of a webhook authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$]__ | Represents the observations of the authenticator's current state. +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. |=== diff --git a/generated/1.17/apis/concierge/authentication/v1alpha1/register.go b/generated/1.17/apis/concierge/authentication/v1alpha1/register.go index 413aa5f6..a00b5e9e 100644 --- a/generated/1.17/apis/concierge/authentication/v1alpha1/register.go +++ b/generated/1.17/apis/concierge/authentication/v1alpha1/register.go @@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &WebhookAuthenticator{}, &WebhookAuthenticatorList{}, + &JWTAuthenticator{}, + &JWTAuthenticatorList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/generated/1.17/apis/concierge/authentication/v1alpha1/types_jwt.go b/generated/1.17/apis/concierge/authentication/v1alpha1/types_jwt.go new file mode 100644 index 00000000..5d0604bd --- /dev/null +++ b/generated/1.17/apis/concierge/authentication/v1alpha1/types_jwt.go @@ -0,0 +1,62 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// Status of a JWT authenticator. +type JWTAuthenticatorStatus struct { + // Represents the observations of the authenticator's current state. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// Spec for configuring a JWT authenticator. +type JWTAuthenticatorSpec struct { + // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // also used to validate the "iss" JWT claim. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^https://` + Issuer string `json:"issuer"` + + // Audience is the required value of the "aud" JWT claim. + // +kubebuilder:validation:MinLength=1 + Audience string `json:"audience"` + + // TLS configuration for communicating with the OIDC provider. + // +optional + TLS *TLSSpec `json:"tls,omitempty"` +} + +// JWTAuthenticator describes the configuration of a JWT authenticator. +// +// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid +// signature, existence of claims, etc.) and extract the username and groups from the token. +// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators +// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` +type JWTAuthenticator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec for configuring the authenticator. + Spec JWTAuthenticatorSpec `json:"spec"` + + // Status of the authenticator. + Status JWTAuthenticatorStatus `json:"status,omitempty"` +} + +// List of JWTAuthenticator objects. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type JWTAuthenticatorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []JWTAuthenticator `json:"items"` +} diff --git a/generated/1.17/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.17/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index a4e27af7..93d4e837 100644 --- a/generated/1.17/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.17/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -28,6 +28,111 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticator. +func (in *JWTAuthenticator) DeepCopy() *JWTAuthenticator { + if in == nil { + return nil + } + out := new(JWTAuthenticator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticator) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorList) DeepCopyInto(out *JWTAuthenticatorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]JWTAuthenticator, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorList. +func (in *JWTAuthenticatorList) DeepCopy() *JWTAuthenticatorList { + if in == nil { + return nil + } + out := new(JWTAuthenticatorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { + *out = *in + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSSpec) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorSpec. +func (in *JWTAuthenticatorSpec) DeepCopy() *JWTAuthenticatorSpec { + if in == nil { + return nil + } + out := new(JWTAuthenticatorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorStatus) DeepCopyInto(out *JWTAuthenticatorStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorStatus. +func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { + if in == nil { + return nil + } + out := new(JWTAuthenticatorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSSpec) DeepCopyInto(out *TLSSpec) { *out = *in diff --git a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go index 13e2dde1..8b208477 100644 --- a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go +++ b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go @@ -13,6 +13,7 @@ import ( type AuthenticationV1alpha1Interface interface { RESTClient() rest.Interface + JWTAuthenticatorsGetter WebhookAuthenticatorsGetter } @@ -21,6 +22,10 @@ type AuthenticationV1alpha1Client struct { restClient rest.Interface } +func (c *AuthenticationV1alpha1Client) JWTAuthenticators(namespace string) JWTAuthenticatorInterface { + return newJWTAuthenticators(c, namespace) +} + func (c *AuthenticationV1alpha1Client) WebhookAuthenticators(namespace string) WebhookAuthenticatorInterface { return newWebhookAuthenticators(c, namespace) } diff --git a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go index e1f2d63b..7b18dd14 100644 --- a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go +++ b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go @@ -15,6 +15,10 @@ type FakeAuthenticationV1alpha1 struct { *testing.Fake } +func (c *FakeAuthenticationV1alpha1) JWTAuthenticators(namespace string) v1alpha1.JWTAuthenticatorInterface { + return &FakeJWTAuthenticators{c, namespace} +} + func (c *FakeAuthenticationV1alpha1) WebhookAuthenticators(namespace string) v1alpha1.WebhookAuthenticatorInterface { return &FakeWebhookAuthenticators{c, namespace} } diff --git a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go new file mode 100644 index 00000000..af138a56 --- /dev/null +++ b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go @@ -0,0 +1,127 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "go.pinniped.dev/generated/1.17/apis/concierge/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeJWTAuthenticators implements JWTAuthenticatorInterface +type FakeJWTAuthenticators struct { + Fake *FakeAuthenticationV1alpha1 + ns string +} + +var jwtauthenticatorsResource = schema.GroupVersionResource{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Resource: "jwtauthenticators"} + +var jwtauthenticatorsKind = schema.GroupVersionKind{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Kind: "JWTAuthenticator"} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *FakeJWTAuthenticators) Get(name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *FakeJWTAuthenticators) List(opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(jwtauthenticatorsResource, jwtauthenticatorsKind, c.ns, opts), &v1alpha1.JWTAuthenticatorList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.JWTAuthenticatorList{ListMeta: obj.(*v1alpha1.JWTAuthenticatorList).ListMeta} + for _, item := range obj.(*v1alpha1.JWTAuthenticatorList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *FakeJWTAuthenticators) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(jwtauthenticatorsResource, c.ns, opts)) + +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Create(jWTAuthenticator *v1alpha1.JWTAuthenticator) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Update(jWTAuthenticator *v1alpha1.JWTAuthenticator) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeJWTAuthenticators) UpdateStatus(jWTAuthenticator *v1alpha1.JWTAuthenticator) (*v1alpha1.JWTAuthenticator, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(jwtauthenticatorsResource, "status", c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *FakeJWTAuthenticators) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeJWTAuthenticators) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(jwtauthenticatorsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.JWTAuthenticatorList{}) + return err +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *FakeJWTAuthenticators) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(jwtauthenticatorsResource, c.ns, name, pt, data, subresources...), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} diff --git a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go index e1b1f2b4..f7d8ccab 100644 --- a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go +++ b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go @@ -5,4 +5,6 @@ package v1alpha1 +type JWTAuthenticatorExpansion interface{} + type WebhookAuthenticatorExpansion interface{} diff --git a/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..9da49bc1 --- /dev/null +++ b/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,178 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "time" + + v1alpha1 "go.pinniped.dev/generated/1.17/apis/concierge/authentication/v1alpha1" + scheme "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// JWTAuthenticatorsGetter has a method to return a JWTAuthenticatorInterface. +// A group's client should implement this interface. +type JWTAuthenticatorsGetter interface { + JWTAuthenticators(namespace string) JWTAuthenticatorInterface +} + +// JWTAuthenticatorInterface has methods to work with JWTAuthenticator resources. +type JWTAuthenticatorInterface interface { + Create(*v1alpha1.JWTAuthenticator) (*v1alpha1.JWTAuthenticator, error) + Update(*v1alpha1.JWTAuthenticator) (*v1alpha1.JWTAuthenticator, error) + UpdateStatus(*v1alpha1.JWTAuthenticator) (*v1alpha1.JWTAuthenticator, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.JWTAuthenticator, error) + List(opts v1.ListOptions) (*v1alpha1.JWTAuthenticatorList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) + JWTAuthenticatorExpansion +} + +// jWTAuthenticators implements JWTAuthenticatorInterface +type jWTAuthenticators struct { + client rest.Interface + ns string +} + +// newJWTAuthenticators returns a JWTAuthenticators +func newJWTAuthenticators(c *AuthenticationV1alpha1Client, namespace string) *jWTAuthenticators { + return &jWTAuthenticators{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *jWTAuthenticators) Get(name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *jWTAuthenticators) List(opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.JWTAuthenticatorList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *jWTAuthenticators) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Create(jWTAuthenticator *v1alpha1.JWTAuthenticator) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Body(jWTAuthenticator). + Do(). + Into(result) + return +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Update(jWTAuthenticator *v1alpha1.JWTAuthenticator) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + Body(jWTAuthenticator). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *jWTAuthenticators) UpdateStatus(jWTAuthenticator *v1alpha1.JWTAuthenticator) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + SubResource("status"). + Body(jWTAuthenticator). + Do(). + Into(result) + return +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *jWTAuthenticators) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jWTAuthenticators) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *jWTAuthenticators) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("jwtauthenticators"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go b/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go index 9afac0fd..2c07b2b2 100644 --- a/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go +++ b/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go @@ -11,6 +11,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // JWTAuthenticators returns a JWTAuthenticatorInformer. + JWTAuthenticators() JWTAuthenticatorInformer // WebhookAuthenticators returns a WebhookAuthenticatorInformer. WebhookAuthenticators() WebhookAuthenticatorInformer } @@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// JWTAuthenticators returns a JWTAuthenticatorInformer. +func (v *version) JWTAuthenticators() JWTAuthenticatorInformer { + return &jWTAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // WebhookAuthenticators returns a WebhookAuthenticatorInformer. func (v *version) WebhookAuthenticators() WebhookAuthenticatorInformer { return &webhookAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go b/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..8dad55a1 --- /dev/null +++ b/generated/1.17/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,76 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + authenticationv1alpha1 "go.pinniped.dev/generated/1.17/apis/concierge/authentication/v1alpha1" + versioned "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned" + internalinterfaces "go.pinniped.dev/generated/1.17/client/concierge/informers/externalversions/internalinterfaces" + v1alpha1 "go.pinniped.dev/generated/1.17/client/concierge/listers/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorInformer provides access to a shared informer and lister for +// JWTAuthenticators. +type JWTAuthenticatorInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.JWTAuthenticatorLister +} + +type jWTAuthenticatorInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).Watch(options) + }, + }, + &authenticationv1alpha1.JWTAuthenticator{}, + resyncPeriod, + indexers, + ) +} + +func (f *jWTAuthenticatorInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *jWTAuthenticatorInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&authenticationv1alpha1.JWTAuthenticator{}, f.defaultInformer) +} + +func (f *jWTAuthenticatorInformer) Lister() v1alpha1.JWTAuthenticatorLister { + return v1alpha1.NewJWTAuthenticatorLister(f.Informer().GetIndexer()) +} diff --git a/generated/1.17/client/concierge/informers/externalversions/generic.go b/generated/1.17/client/concierge/informers/externalversions/generic.go index ed7cade5..0d4217f3 100644 --- a/generated/1.17/client/concierge/informers/externalversions/generic.go +++ b/generated/1.17/client/concierge/informers/externalversions/generic.go @@ -42,6 +42,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=authentication.concierge.pinniped.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("jwtauthenticators"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().JWTAuthenticators().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("webhookauthenticators"): return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().WebhookAuthenticators().Informer()}, nil diff --git a/generated/1.17/client/concierge/listers/authentication/v1alpha1/expansion_generated.go b/generated/1.17/client/concierge/listers/authentication/v1alpha1/expansion_generated.go index 25e1ad66..8e816c71 100644 --- a/generated/1.17/client/concierge/listers/authentication/v1alpha1/expansion_generated.go +++ b/generated/1.17/client/concierge/listers/authentication/v1alpha1/expansion_generated.go @@ -5,6 +5,14 @@ package v1alpha1 +// JWTAuthenticatorListerExpansion allows custom methods to be added to +// JWTAuthenticatorLister. +type JWTAuthenticatorListerExpansion interface{} + +// JWTAuthenticatorNamespaceListerExpansion allows custom methods to be added to +// JWTAuthenticatorNamespaceLister. +type JWTAuthenticatorNamespaceListerExpansion interface{} + // WebhookAuthenticatorListerExpansion allows custom methods to be added to // WebhookAuthenticatorLister. type WebhookAuthenticatorListerExpansion interface{} diff --git a/generated/1.17/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go b/generated/1.17/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..267294f4 --- /dev/null +++ b/generated/1.17/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,81 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "go.pinniped.dev/generated/1.17/apis/concierge/authentication/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorLister helps list JWTAuthenticators. +type JWTAuthenticatorLister interface { + // List lists all JWTAuthenticators in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // JWTAuthenticators returns an object that can list and get JWTAuthenticators. + JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister + JWTAuthenticatorListerExpansion +} + +// jWTAuthenticatorLister implements the JWTAuthenticatorLister interface. +type jWTAuthenticatorLister struct { + indexer cache.Indexer +} + +// NewJWTAuthenticatorLister returns a new JWTAuthenticatorLister. +func NewJWTAuthenticatorLister(indexer cache.Indexer) JWTAuthenticatorLister { + return &jWTAuthenticatorLister{indexer: indexer} +} + +// List lists all JWTAuthenticators in the indexer. +func (s *jWTAuthenticatorLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// JWTAuthenticators returns an object that can list and get JWTAuthenticators. +func (s *jWTAuthenticatorLister) JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister { + return jWTAuthenticatorNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// JWTAuthenticatorNamespaceLister helps list and get JWTAuthenticators. +type JWTAuthenticatorNamespaceLister interface { + // List lists all JWTAuthenticators in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.JWTAuthenticator, error) + JWTAuthenticatorNamespaceListerExpansion +} + +// jWTAuthenticatorNamespaceLister implements the JWTAuthenticatorNamespaceLister +// interface. +type jWTAuthenticatorNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all JWTAuthenticators in the indexer for a given namespace. +func (s jWTAuthenticatorNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. +func (s jWTAuthenticatorNamespaceLister) Get(name string) (*v1alpha1.JWTAuthenticator, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("jwtauthenticator"), name) + } + return obj.(*v1alpha1.JWTAuthenticator), nil +} diff --git a/generated/1.17/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.17/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml new file mode 100644 index 00000000..ed16bf0c --- /dev/null +++ b/generated/1.17/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -0,0 +1,155 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: jwtauthenticators.authentication.concierge.pinniped.dev +spec: + group: authentication.concierge.pinniped.dev + names: + categories: + - pinniped + - pinniped-authenticator + - pinniped-authenticators + kind: JWTAuthenticator + listKind: JWTAuthenticatorList + plural: jwtauthenticators + singular: jwtauthenticator + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.issuer + name: Issuer + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "JWTAuthenticator describes the configuration of a JWT authenticator. + \n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation + on it (e.g., valid signature, existence of claims, etc.) and extract the + username and groups from the token." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec for configuring the authenticator. + properties: + audience: + description: Audience is the required value of the "aud" JWT claim. + minLength: 1 + type: string + issuer: + description: Issuer is the OIDC issuer URL that will be used to discover + public signing keys. Issuer is also used to validate the "iss" JWT + claim. + minLength: 1 + pattern: ^https:// + type: string + tls: + description: TLS configuration for communicating with the OIDC provider. + properties: + certificateAuthorityData: + description: X.509 Certificate Authority (base64-encoded PEM bundle). + If omitted, a default set of system roots will be trusted. + type: string + type: object + required: + - audience + - issuer + type: object + status: + description: Status of the authenticator. + properties: + conditions: + description: Represents the observations of the authenticator's current + state. + items: + description: Condition status of a resource (mirrored from the metav1.Condition + type added in Kubernetes 1.19). In a future API version we can + switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index a2a20313..97042b25 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -22,10 +22,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio [id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-condition"] ==== Condition -Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$] **** @@ -41,13 +42,85 @@ Condition status of a resource (mirrored from the metav1.Condition type added in |=== -[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-tlsspec"] -==== TLSSpec +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-conditionstatus"] +==== ConditionStatus (string) + -Configuration for configuring TLS on various authenticators. .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticator"] +==== JWTAuthenticator + +JWTAuthenticator describes the configuration of a JWT authenticator. + Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid signature, existence of claims, etc.) and extract the username and groups from the token. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorlist[$$JWTAuthenticatorList$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. +|=== + + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] +==== JWTAuthenticatorSpec + +Spec for configuring a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. +| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] +==== JWTAuthenticatorStatus + +Status of a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-tlsspec"] +==== TLSSpec + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec[$$WebhookAuthenticatorSpec$$] **** @@ -111,7 +184,7 @@ Status of a webhook authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-condition[$$Condition$$]__ | Represents the observations of the authenticator's current state. +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-18-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. |=== diff --git a/generated/1.18/apis/concierge/authentication/v1alpha1/register.go b/generated/1.18/apis/concierge/authentication/v1alpha1/register.go index 413aa5f6..a00b5e9e 100644 --- a/generated/1.18/apis/concierge/authentication/v1alpha1/register.go +++ b/generated/1.18/apis/concierge/authentication/v1alpha1/register.go @@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &WebhookAuthenticator{}, &WebhookAuthenticatorList{}, + &JWTAuthenticator{}, + &JWTAuthenticatorList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/generated/1.18/apis/concierge/authentication/v1alpha1/types_jwt.go b/generated/1.18/apis/concierge/authentication/v1alpha1/types_jwt.go new file mode 100644 index 00000000..5d0604bd --- /dev/null +++ b/generated/1.18/apis/concierge/authentication/v1alpha1/types_jwt.go @@ -0,0 +1,62 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// Status of a JWT authenticator. +type JWTAuthenticatorStatus struct { + // Represents the observations of the authenticator's current state. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// Spec for configuring a JWT authenticator. +type JWTAuthenticatorSpec struct { + // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // also used to validate the "iss" JWT claim. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^https://` + Issuer string `json:"issuer"` + + // Audience is the required value of the "aud" JWT claim. + // +kubebuilder:validation:MinLength=1 + Audience string `json:"audience"` + + // TLS configuration for communicating with the OIDC provider. + // +optional + TLS *TLSSpec `json:"tls,omitempty"` +} + +// JWTAuthenticator describes the configuration of a JWT authenticator. +// +// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid +// signature, existence of claims, etc.) and extract the username and groups from the token. +// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators +// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` +type JWTAuthenticator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec for configuring the authenticator. + Spec JWTAuthenticatorSpec `json:"spec"` + + // Status of the authenticator. + Status JWTAuthenticatorStatus `json:"status,omitempty"` +} + +// List of JWTAuthenticator objects. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type JWTAuthenticatorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []JWTAuthenticator `json:"items"` +} diff --git a/generated/1.18/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.18/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index a4e27af7..93d4e837 100644 --- a/generated/1.18/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.18/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -28,6 +28,111 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticator. +func (in *JWTAuthenticator) DeepCopy() *JWTAuthenticator { + if in == nil { + return nil + } + out := new(JWTAuthenticator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticator) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorList) DeepCopyInto(out *JWTAuthenticatorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]JWTAuthenticator, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorList. +func (in *JWTAuthenticatorList) DeepCopy() *JWTAuthenticatorList { + if in == nil { + return nil + } + out := new(JWTAuthenticatorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { + *out = *in + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSSpec) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorSpec. +func (in *JWTAuthenticatorSpec) DeepCopy() *JWTAuthenticatorSpec { + if in == nil { + return nil + } + out := new(JWTAuthenticatorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorStatus) DeepCopyInto(out *JWTAuthenticatorStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorStatus. +func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { + if in == nil { + return nil + } + out := new(JWTAuthenticatorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSSpec) DeepCopyInto(out *TLSSpec) { *out = *in diff --git a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go index 1d43d7dd..f88ebf7e 100644 --- a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go +++ b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go @@ -13,6 +13,7 @@ import ( type AuthenticationV1alpha1Interface interface { RESTClient() rest.Interface + JWTAuthenticatorsGetter WebhookAuthenticatorsGetter } @@ -21,6 +22,10 @@ type AuthenticationV1alpha1Client struct { restClient rest.Interface } +func (c *AuthenticationV1alpha1Client) JWTAuthenticators(namespace string) JWTAuthenticatorInterface { + return newJWTAuthenticators(c, namespace) +} + func (c *AuthenticationV1alpha1Client) WebhookAuthenticators(namespace string) WebhookAuthenticatorInterface { return newWebhookAuthenticators(c, namespace) } diff --git a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go index d0c213fd..fafe5d5d 100644 --- a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go +++ b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go @@ -15,6 +15,10 @@ type FakeAuthenticationV1alpha1 struct { *testing.Fake } +func (c *FakeAuthenticationV1alpha1) JWTAuthenticators(namespace string) v1alpha1.JWTAuthenticatorInterface { + return &FakeJWTAuthenticators{c, namespace} +} + func (c *FakeAuthenticationV1alpha1) WebhookAuthenticators(namespace string) v1alpha1.WebhookAuthenticatorInterface { return &FakeWebhookAuthenticators{c, namespace} } diff --git a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go new file mode 100644 index 00000000..dfbdc7d9 --- /dev/null +++ b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go @@ -0,0 +1,129 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "go.pinniped.dev/generated/1.18/apis/concierge/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeJWTAuthenticators implements JWTAuthenticatorInterface +type FakeJWTAuthenticators struct { + Fake *FakeAuthenticationV1alpha1 + ns string +} + +var jwtauthenticatorsResource = schema.GroupVersionResource{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Resource: "jwtauthenticators"} + +var jwtauthenticatorsKind = schema.GroupVersionKind{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Kind: "JWTAuthenticator"} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *FakeJWTAuthenticators) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *FakeJWTAuthenticators) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(jwtauthenticatorsResource, jwtauthenticatorsKind, c.ns, opts), &v1alpha1.JWTAuthenticatorList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.JWTAuthenticatorList{ListMeta: obj.(*v1alpha1.JWTAuthenticatorList).ListMeta} + for _, item := range obj.(*v1alpha1.JWTAuthenticatorList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *FakeJWTAuthenticators) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(jwtauthenticatorsResource, c.ns, opts)) + +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeJWTAuthenticators) UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(jwtauthenticatorsResource, "status", c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *FakeJWTAuthenticators) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeJWTAuthenticators) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(jwtauthenticatorsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.JWTAuthenticatorList{}) + return err +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *FakeJWTAuthenticators) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(jwtauthenticatorsResource, c.ns, name, pt, data, subresources...), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} diff --git a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go index e1b1f2b4..f7d8ccab 100644 --- a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go +++ b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go @@ -5,4 +5,6 @@ package v1alpha1 +type JWTAuthenticatorExpansion interface{} + type WebhookAuthenticatorExpansion interface{} diff --git a/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..3ad08674 --- /dev/null +++ b/generated/1.18/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,182 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "go.pinniped.dev/generated/1.18/apis/concierge/authentication/v1alpha1" + scheme "go.pinniped.dev/generated/1.18/client/concierge/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// JWTAuthenticatorsGetter has a method to return a JWTAuthenticatorInterface. +// A group's client should implement this interface. +type JWTAuthenticatorsGetter interface { + JWTAuthenticators(namespace string) JWTAuthenticatorInterface +} + +// JWTAuthenticatorInterface has methods to work with JWTAuthenticator resources. +type JWTAuthenticatorInterface interface { + Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (*v1alpha1.JWTAuthenticator, error) + Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) + UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.JWTAuthenticator, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.JWTAuthenticatorList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) + JWTAuthenticatorExpansion +} + +// jWTAuthenticators implements JWTAuthenticatorInterface +type jWTAuthenticators struct { + client rest.Interface + ns string +} + +// newJWTAuthenticators returns a JWTAuthenticators +func newJWTAuthenticators(c *AuthenticationV1alpha1Client, namespace string) *jWTAuthenticators { + return &jWTAuthenticators{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *jWTAuthenticators) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *jWTAuthenticators) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.JWTAuthenticatorList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *jWTAuthenticators) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *jWTAuthenticators) UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *jWTAuthenticators) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jWTAuthenticators) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *jWTAuthenticators) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go b/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go index 87e0f9cd..debe7c93 100644 --- a/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go +++ b/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go @@ -11,6 +11,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // JWTAuthenticators returns a JWTAuthenticatorInformer. + JWTAuthenticators() JWTAuthenticatorInformer // WebhookAuthenticators returns a WebhookAuthenticatorInformer. WebhookAuthenticators() WebhookAuthenticatorInformer } @@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// JWTAuthenticators returns a JWTAuthenticatorInformer. +func (v *version) JWTAuthenticators() JWTAuthenticatorInformer { + return &jWTAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // WebhookAuthenticators returns a WebhookAuthenticatorInformer. func (v *version) WebhookAuthenticators() WebhookAuthenticatorInformer { return &webhookAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go b/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..6f25f276 --- /dev/null +++ b/generated/1.18/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,77 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + authenticationv1alpha1 "go.pinniped.dev/generated/1.18/apis/concierge/authentication/v1alpha1" + versioned "go.pinniped.dev/generated/1.18/client/concierge/clientset/versioned" + internalinterfaces "go.pinniped.dev/generated/1.18/client/concierge/informers/externalversions/internalinterfaces" + v1alpha1 "go.pinniped.dev/generated/1.18/client/concierge/listers/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorInformer provides access to a shared informer and lister for +// JWTAuthenticators. +type JWTAuthenticatorInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.JWTAuthenticatorLister +} + +type jWTAuthenticatorInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).Watch(context.TODO(), options) + }, + }, + &authenticationv1alpha1.JWTAuthenticator{}, + resyncPeriod, + indexers, + ) +} + +func (f *jWTAuthenticatorInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *jWTAuthenticatorInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&authenticationv1alpha1.JWTAuthenticator{}, f.defaultInformer) +} + +func (f *jWTAuthenticatorInformer) Lister() v1alpha1.JWTAuthenticatorLister { + return v1alpha1.NewJWTAuthenticatorLister(f.Informer().GetIndexer()) +} diff --git a/generated/1.18/client/concierge/informers/externalversions/generic.go b/generated/1.18/client/concierge/informers/externalversions/generic.go index 89a09692..4ad85087 100644 --- a/generated/1.18/client/concierge/informers/externalversions/generic.go +++ b/generated/1.18/client/concierge/informers/externalversions/generic.go @@ -42,6 +42,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=authentication.concierge.pinniped.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("jwtauthenticators"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().JWTAuthenticators().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("webhookauthenticators"): return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().WebhookAuthenticators().Informer()}, nil diff --git a/generated/1.18/client/concierge/listers/authentication/v1alpha1/expansion_generated.go b/generated/1.18/client/concierge/listers/authentication/v1alpha1/expansion_generated.go index 25e1ad66..8e816c71 100644 --- a/generated/1.18/client/concierge/listers/authentication/v1alpha1/expansion_generated.go +++ b/generated/1.18/client/concierge/listers/authentication/v1alpha1/expansion_generated.go @@ -5,6 +5,14 @@ package v1alpha1 +// JWTAuthenticatorListerExpansion allows custom methods to be added to +// JWTAuthenticatorLister. +type JWTAuthenticatorListerExpansion interface{} + +// JWTAuthenticatorNamespaceListerExpansion allows custom methods to be added to +// JWTAuthenticatorNamespaceLister. +type JWTAuthenticatorNamespaceListerExpansion interface{} + // WebhookAuthenticatorListerExpansion allows custom methods to be added to // WebhookAuthenticatorLister. type WebhookAuthenticatorListerExpansion interface{} diff --git a/generated/1.18/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go b/generated/1.18/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..62b6f032 --- /dev/null +++ b/generated/1.18/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,81 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "go.pinniped.dev/generated/1.18/apis/concierge/authentication/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorLister helps list JWTAuthenticators. +type JWTAuthenticatorLister interface { + // List lists all JWTAuthenticators in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // JWTAuthenticators returns an object that can list and get JWTAuthenticators. + JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister + JWTAuthenticatorListerExpansion +} + +// jWTAuthenticatorLister implements the JWTAuthenticatorLister interface. +type jWTAuthenticatorLister struct { + indexer cache.Indexer +} + +// NewJWTAuthenticatorLister returns a new JWTAuthenticatorLister. +func NewJWTAuthenticatorLister(indexer cache.Indexer) JWTAuthenticatorLister { + return &jWTAuthenticatorLister{indexer: indexer} +} + +// List lists all JWTAuthenticators in the indexer. +func (s *jWTAuthenticatorLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// JWTAuthenticators returns an object that can list and get JWTAuthenticators. +func (s *jWTAuthenticatorLister) JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister { + return jWTAuthenticatorNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// JWTAuthenticatorNamespaceLister helps list and get JWTAuthenticators. +type JWTAuthenticatorNamespaceLister interface { + // List lists all JWTAuthenticators in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.JWTAuthenticator, error) + JWTAuthenticatorNamespaceListerExpansion +} + +// jWTAuthenticatorNamespaceLister implements the JWTAuthenticatorNamespaceLister +// interface. +type jWTAuthenticatorNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all JWTAuthenticators in the indexer for a given namespace. +func (s jWTAuthenticatorNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. +func (s jWTAuthenticatorNamespaceLister) Get(name string) (*v1alpha1.JWTAuthenticator, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("jwtauthenticator"), name) + } + return obj.(*v1alpha1.JWTAuthenticator), nil +} diff --git a/generated/1.18/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.18/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml new file mode 100644 index 00000000..ed16bf0c --- /dev/null +++ b/generated/1.18/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -0,0 +1,155 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: jwtauthenticators.authentication.concierge.pinniped.dev +spec: + group: authentication.concierge.pinniped.dev + names: + categories: + - pinniped + - pinniped-authenticator + - pinniped-authenticators + kind: JWTAuthenticator + listKind: JWTAuthenticatorList + plural: jwtauthenticators + singular: jwtauthenticator + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.issuer + name: Issuer + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "JWTAuthenticator describes the configuration of a JWT authenticator. + \n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation + on it (e.g., valid signature, existence of claims, etc.) and extract the + username and groups from the token." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec for configuring the authenticator. + properties: + audience: + description: Audience is the required value of the "aud" JWT claim. + minLength: 1 + type: string + issuer: + description: Issuer is the OIDC issuer URL that will be used to discover + public signing keys. Issuer is also used to validate the "iss" JWT + claim. + minLength: 1 + pattern: ^https:// + type: string + tls: + description: TLS configuration for communicating with the OIDC provider. + properties: + certificateAuthorityData: + description: X.509 Certificate Authority (base64-encoded PEM bundle). + If omitted, a default set of system roots will be trusted. + type: string + type: object + required: + - audience + - issuer + type: object + status: + description: Status of the authenticator. + properties: + conditions: + description: Represents the observations of the authenticator's current + state. + items: + description: Condition status of a resource (mirrored from the metav1.Condition + type added in Kubernetes 1.19). In a future API version we can + switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index e36be991..edda33b8 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -22,10 +22,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio [id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-condition"] ==== Condition -Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$] **** @@ -41,13 +42,85 @@ Condition status of a resource (mirrored from the metav1.Condition type added in |=== -[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-tlsspec"] -==== TLSSpec +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-conditionstatus"] +==== ConditionStatus (string) + -Configuration for configuring TLS on various authenticators. .Appears In: **** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] +**** + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator"] +==== JWTAuthenticator + +JWTAuthenticator describes the configuration of a JWT authenticator. + Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid signature, existence of claims, etc.) and extract the username and groups from the token. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorlist[$$JWTAuthenticatorList$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. + +| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator. +| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator. +|=== + + + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"] +==== JWTAuthenticatorSpec + +Spec for configuring a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim. +| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim. +| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"] +==== JWTAuthenticatorStatus + +Status of a JWT authenticator. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. +|=== + + +[id="{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-tlsspec"] +==== TLSSpec + + + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$] - xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec[$$WebhookAuthenticatorSpec$$] **** @@ -111,7 +184,7 @@ Status of a webhook authenticator. [cols="25a,75a", options="header"] |=== | Field | Description -| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-condition[$$Condition$$]__ | Represents the observations of the authenticator's current state. +| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state. |=== diff --git a/generated/1.19/apis/concierge/authentication/v1alpha1/register.go b/generated/1.19/apis/concierge/authentication/v1alpha1/register.go index 413aa5f6..a00b5e9e 100644 --- a/generated/1.19/apis/concierge/authentication/v1alpha1/register.go +++ b/generated/1.19/apis/concierge/authentication/v1alpha1/register.go @@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &WebhookAuthenticator{}, &WebhookAuthenticatorList{}, + &JWTAuthenticator{}, + &JWTAuthenticatorList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/generated/1.19/apis/concierge/authentication/v1alpha1/types_jwt.go b/generated/1.19/apis/concierge/authentication/v1alpha1/types_jwt.go new file mode 100644 index 00000000..5d0604bd --- /dev/null +++ b/generated/1.19/apis/concierge/authentication/v1alpha1/types_jwt.go @@ -0,0 +1,62 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// Status of a JWT authenticator. +type JWTAuthenticatorStatus struct { + // Represents the observations of the authenticator's current state. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// Spec for configuring a JWT authenticator. +type JWTAuthenticatorSpec struct { + // Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is + // also used to validate the "iss" JWT claim. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^https://` + Issuer string `json:"issuer"` + + // Audience is the required value of the "aud" JWT claim. + // +kubebuilder:validation:MinLength=1 + Audience string `json:"audience"` + + // TLS configuration for communicating with the OIDC provider. + // +optional + TLS *TLSSpec `json:"tls,omitempty"` +} + +// JWTAuthenticator describes the configuration of a JWT authenticator. +// +// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid +// signature, existence of claims, etc.) and extract the username and groups from the token. +// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators +// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` +type JWTAuthenticator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec for configuring the authenticator. + Spec JWTAuthenticatorSpec `json:"spec"` + + // Status of the authenticator. + Status JWTAuthenticatorStatus `json:"status,omitempty"` +} + +// List of JWTAuthenticator objects. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type JWTAuthenticatorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []JWTAuthenticator `json:"items"` +} diff --git a/generated/1.19/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go b/generated/1.19/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go index a4e27af7..93d4e837 100644 --- a/generated/1.19/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.19/apis/concierge/authentication/v1alpha1/zz_generated.deepcopy.go @@ -28,6 +28,111 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticator. +func (in *JWTAuthenticator) DeepCopy() *JWTAuthenticator { + if in == nil { + return nil + } + out := new(JWTAuthenticator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticator) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorList) DeepCopyInto(out *JWTAuthenticatorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]JWTAuthenticator, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorList. +func (in *JWTAuthenticatorList) DeepCopy() *JWTAuthenticatorList { + if in == nil { + return nil + } + out := new(JWTAuthenticatorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) { + *out = *in + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSSpec) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorSpec. +func (in *JWTAuthenticatorSpec) DeepCopy() *JWTAuthenticatorSpec { + if in == nil { + return nil + } + out := new(JWTAuthenticatorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTAuthenticatorStatus) DeepCopyInto(out *JWTAuthenticatorStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorStatus. +func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus { + if in == nil { + return nil + } + out := new(JWTAuthenticatorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSSpec) DeepCopyInto(out *TLSSpec) { *out = *in diff --git a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go index 5d103d63..58968bcc 100644 --- a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go +++ b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/authentication_client.go @@ -13,6 +13,7 @@ import ( type AuthenticationV1alpha1Interface interface { RESTClient() rest.Interface + JWTAuthenticatorsGetter WebhookAuthenticatorsGetter } @@ -21,6 +22,10 @@ type AuthenticationV1alpha1Client struct { restClient rest.Interface } +func (c *AuthenticationV1alpha1Client) JWTAuthenticators(namespace string) JWTAuthenticatorInterface { + return newJWTAuthenticators(c, namespace) +} + func (c *AuthenticationV1alpha1Client) WebhookAuthenticators(namespace string) WebhookAuthenticatorInterface { return newWebhookAuthenticators(c, namespace) } diff --git a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go index 17efb220..6de79a36 100644 --- a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go +++ b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_authentication_client.go @@ -15,6 +15,10 @@ type FakeAuthenticationV1alpha1 struct { *testing.Fake } +func (c *FakeAuthenticationV1alpha1) JWTAuthenticators(namespace string) v1alpha1.JWTAuthenticatorInterface { + return &FakeJWTAuthenticators{c, namespace} +} + func (c *FakeAuthenticationV1alpha1) WebhookAuthenticators(namespace string) v1alpha1.WebhookAuthenticatorInterface { return &FakeWebhookAuthenticators{c, namespace} } diff --git a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go new file mode 100644 index 00000000..c2978cc2 --- /dev/null +++ b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/fake/fake_jwtauthenticator.go @@ -0,0 +1,129 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeJWTAuthenticators implements JWTAuthenticatorInterface +type FakeJWTAuthenticators struct { + Fake *FakeAuthenticationV1alpha1 + ns string +} + +var jwtauthenticatorsResource = schema.GroupVersionResource{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Resource: "jwtauthenticators"} + +var jwtauthenticatorsKind = schema.GroupVersionKind{Group: "authentication.concierge.pinniped.dev", Version: "v1alpha1", Kind: "JWTAuthenticator"} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *FakeJWTAuthenticators) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *FakeJWTAuthenticators) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(jwtauthenticatorsResource, jwtauthenticatorsKind, c.ns, opts), &v1alpha1.JWTAuthenticatorList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.JWTAuthenticatorList{ListMeta: obj.(*v1alpha1.JWTAuthenticatorList).ListMeta} + for _, item := range obj.(*v1alpha1.JWTAuthenticatorList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *FakeJWTAuthenticators) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(jwtauthenticatorsResource, c.ns, opts)) + +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *FakeJWTAuthenticators) Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(jwtauthenticatorsResource, c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeJWTAuthenticators) UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(jwtauthenticatorsResource, "status", c.ns, jWTAuthenticator), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *FakeJWTAuthenticators) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(jwtauthenticatorsResource, c.ns, name), &v1alpha1.JWTAuthenticator{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeJWTAuthenticators) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(jwtauthenticatorsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.JWTAuthenticatorList{}) + return err +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *FakeJWTAuthenticators) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(jwtauthenticatorsResource, c.ns, name, pt, data, subresources...), &v1alpha1.JWTAuthenticator{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.JWTAuthenticator), err +} diff --git a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go index e1b1f2b4..f7d8ccab 100644 --- a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go +++ b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/generated_expansion.go @@ -5,4 +5,6 @@ package v1alpha1 +type JWTAuthenticatorExpansion interface{} + type WebhookAuthenticatorExpansion interface{} diff --git a/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..e540fcaa --- /dev/null +++ b/generated/1.19/client/concierge/clientset/versioned/typed/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,182 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + scheme "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// JWTAuthenticatorsGetter has a method to return a JWTAuthenticatorInterface. +// A group's client should implement this interface. +type JWTAuthenticatorsGetter interface { + JWTAuthenticators(namespace string) JWTAuthenticatorInterface +} + +// JWTAuthenticatorInterface has methods to work with JWTAuthenticator resources. +type JWTAuthenticatorInterface interface { + Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (*v1alpha1.JWTAuthenticator, error) + Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) + UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (*v1alpha1.JWTAuthenticator, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.JWTAuthenticator, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.JWTAuthenticatorList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) + JWTAuthenticatorExpansion +} + +// jWTAuthenticators implements JWTAuthenticatorInterface +type jWTAuthenticators struct { + client rest.Interface + ns string +} + +// newJWTAuthenticators returns a JWTAuthenticators +func newJWTAuthenticators(c *AuthenticationV1alpha1Client, namespace string) *jWTAuthenticators { + return &jWTAuthenticators{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the jWTAuthenticator, and returns the corresponding jWTAuthenticator object, and an error if there is any. +func (c *jWTAuthenticators) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of JWTAuthenticators that match those selectors. +func (c *jWTAuthenticators) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.JWTAuthenticatorList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.JWTAuthenticatorList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jWTAuthenticators. +func (c *jWTAuthenticators) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a jWTAuthenticator and creates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Create(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.CreateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a jWTAuthenticator and updates it. Returns the server's representation of the jWTAuthenticator, and an error, if there is any. +func (c *jWTAuthenticators) Update(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *jWTAuthenticators) UpdateStatus(ctx context.Context, jWTAuthenticator *v1alpha1.JWTAuthenticator, opts v1.UpdateOptions) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(jWTAuthenticator.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(jWTAuthenticator). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the jWTAuthenticator and deletes it. Returns an error if one occurs. +func (c *jWTAuthenticators) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jWTAuthenticators) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("jwtauthenticators"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched jWTAuthenticator. +func (c *jWTAuthenticators) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.JWTAuthenticator, err error) { + result = &v1alpha1.JWTAuthenticator{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("jwtauthenticators"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go b/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go index 2497f64f..95a59311 100644 --- a/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go +++ b/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/interface.go @@ -11,6 +11,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // JWTAuthenticators returns a JWTAuthenticatorInformer. + JWTAuthenticators() JWTAuthenticatorInformer // WebhookAuthenticators returns a WebhookAuthenticatorInformer. WebhookAuthenticators() WebhookAuthenticatorInformer } @@ -26,6 +28,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// JWTAuthenticators returns a JWTAuthenticatorInformer. +func (v *version) JWTAuthenticators() JWTAuthenticatorInformer { + return &jWTAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // WebhookAuthenticators returns a WebhookAuthenticatorInformer. func (v *version) WebhookAuthenticators() WebhookAuthenticatorInformer { return &webhookAuthenticatorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go b/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..994e1d5d --- /dev/null +++ b/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,77 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + authenticationv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + versioned "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned" + internalinterfaces "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/internalinterfaces" + v1alpha1 "go.pinniped.dev/generated/1.19/client/concierge/listers/authentication/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorInformer provides access to a shared informer and lister for +// JWTAuthenticators. +type JWTAuthenticatorInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.JWTAuthenticatorLister +} + +type jWTAuthenticatorInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredJWTAuthenticatorInformer constructs a new informer for JWTAuthenticator type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredJWTAuthenticatorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AuthenticationV1alpha1().JWTAuthenticators(namespace).Watch(context.TODO(), options) + }, + }, + &authenticationv1alpha1.JWTAuthenticator{}, + resyncPeriod, + indexers, + ) +} + +func (f *jWTAuthenticatorInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredJWTAuthenticatorInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *jWTAuthenticatorInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&authenticationv1alpha1.JWTAuthenticator{}, f.defaultInformer) +} + +func (f *jWTAuthenticatorInformer) Lister() v1alpha1.JWTAuthenticatorLister { + return v1alpha1.NewJWTAuthenticatorLister(f.Informer().GetIndexer()) +} diff --git a/generated/1.19/client/concierge/informers/externalversions/generic.go b/generated/1.19/client/concierge/informers/externalversions/generic.go index 14fca38c..90a921d1 100644 --- a/generated/1.19/client/concierge/informers/externalversions/generic.go +++ b/generated/1.19/client/concierge/informers/externalversions/generic.go @@ -42,6 +42,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=authentication.concierge.pinniped.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("jwtauthenticators"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().JWTAuthenticators().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("webhookauthenticators"): return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().WebhookAuthenticators().Informer()}, nil diff --git a/generated/1.19/client/concierge/listers/authentication/v1alpha1/expansion_generated.go b/generated/1.19/client/concierge/listers/authentication/v1alpha1/expansion_generated.go index 25e1ad66..8e816c71 100644 --- a/generated/1.19/client/concierge/listers/authentication/v1alpha1/expansion_generated.go +++ b/generated/1.19/client/concierge/listers/authentication/v1alpha1/expansion_generated.go @@ -5,6 +5,14 @@ package v1alpha1 +// JWTAuthenticatorListerExpansion allows custom methods to be added to +// JWTAuthenticatorLister. +type JWTAuthenticatorListerExpansion interface{} + +// JWTAuthenticatorNamespaceListerExpansion allows custom methods to be added to +// JWTAuthenticatorNamespaceLister. +type JWTAuthenticatorNamespaceListerExpansion interface{} + // WebhookAuthenticatorListerExpansion allows custom methods to be added to // WebhookAuthenticatorLister. type WebhookAuthenticatorListerExpansion interface{} diff --git a/generated/1.19/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go b/generated/1.19/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go new file mode 100644 index 00000000..6d0291ed --- /dev/null +++ b/generated/1.19/client/concierge/listers/authentication/v1alpha1/jwtauthenticator.go @@ -0,0 +1,86 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// JWTAuthenticatorLister helps list JWTAuthenticators. +// All objects returned here must be treated as read-only. +type JWTAuthenticatorLister interface { + // List lists all JWTAuthenticators in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // JWTAuthenticators returns an object that can list and get JWTAuthenticators. + JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister + JWTAuthenticatorListerExpansion +} + +// jWTAuthenticatorLister implements the JWTAuthenticatorLister interface. +type jWTAuthenticatorLister struct { + indexer cache.Indexer +} + +// NewJWTAuthenticatorLister returns a new JWTAuthenticatorLister. +func NewJWTAuthenticatorLister(indexer cache.Indexer) JWTAuthenticatorLister { + return &jWTAuthenticatorLister{indexer: indexer} +} + +// List lists all JWTAuthenticators in the indexer. +func (s *jWTAuthenticatorLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// JWTAuthenticators returns an object that can list and get JWTAuthenticators. +func (s *jWTAuthenticatorLister) JWTAuthenticators(namespace string) JWTAuthenticatorNamespaceLister { + return jWTAuthenticatorNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// JWTAuthenticatorNamespaceLister helps list and get JWTAuthenticators. +// All objects returned here must be treated as read-only. +type JWTAuthenticatorNamespaceLister interface { + // List lists all JWTAuthenticators in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) + // Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.JWTAuthenticator, error) + JWTAuthenticatorNamespaceListerExpansion +} + +// jWTAuthenticatorNamespaceLister implements the JWTAuthenticatorNamespaceLister +// interface. +type jWTAuthenticatorNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all JWTAuthenticators in the indexer for a given namespace. +func (s jWTAuthenticatorNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.JWTAuthenticator, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.JWTAuthenticator)) + }) + return ret, err +} + +// Get retrieves the JWTAuthenticator from the indexer for a given namespace and name. +func (s jWTAuthenticatorNamespaceLister) Get(name string) (*v1alpha1.JWTAuthenticator, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("jwtauthenticator"), name) + } + return obj.(*v1alpha1.JWTAuthenticator), nil +} diff --git a/generated/1.19/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml b/generated/1.19/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml new file mode 100644 index 00000000..ed16bf0c --- /dev/null +++ b/generated/1.19/crds/authentication.concierge.pinniped.dev_jwtauthenticators.yaml @@ -0,0 +1,155 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: jwtauthenticators.authentication.concierge.pinniped.dev +spec: + group: authentication.concierge.pinniped.dev + names: + categories: + - pinniped + - pinniped-authenticator + - pinniped-authenticators + kind: JWTAuthenticator + listKind: JWTAuthenticatorList + plural: jwtauthenticators + singular: jwtauthenticator + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.issuer + name: Issuer + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: "JWTAuthenticator describes the configuration of a JWT authenticator. + \n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation + on it (e.g., valid signature, existence of claims, etc.) and extract the + username and groups from the token." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec for configuring the authenticator. + properties: + audience: + description: Audience is the required value of the "aud" JWT claim. + minLength: 1 + type: string + issuer: + description: Issuer is the OIDC issuer URL that will be used to discover + public signing keys. Issuer is also used to validate the "iss" JWT + claim. + minLength: 1 + pattern: ^https:// + type: string + tls: + description: TLS configuration for communicating with the OIDC provider. + properties: + certificateAuthorityData: + description: X.509 Certificate Authority (base64-encoded PEM bundle). + If omitted, a default set of system roots will be trusted. + type: string + type: object + required: + - audience + - issuer + type: object + status: + description: Status of the authenticator. + properties: + conditions: + description: Represents the observations of the authenticator's current + state. + items: + description: Condition status of a resource (mirrored from the metav1.Condition + type added in Kubernetes 1.19). In a future API version we can + switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413. + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/test/integration/kube_api_discovery_test.go b/test/integration/kube_api_discovery_test.go index d04b1b42..cefa4964 100644 --- a/test/integration/kube_api_discovery_test.go +++ b/test/integration/kube_api_discovery_test.go @@ -162,6 +162,14 @@ func TestGetAPIResourceList(t *testing.T) { Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}, Categories: []string{"pinniped", "pinniped-authenticator", "pinniped-authenticators"}, }, + { + Name: "jwtauthenticators", + SingularName: "jwtauthenticator", + Namespaced: true, + Kind: "JWTAuthenticator", + Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}, + Categories: []string{"pinniped", "pinniped-authenticator", "pinniped-authenticators"}, + }, }, }, }, From 57103e0a9fb605cb2bd0daea67c4eee7f95684be Mon Sep 17 00:00:00 2001 From: Andrew Keesler Date: Mon, 7 Dec 2020 20:39:51 -0500 Subject: [PATCH 2/6] Add JWTAuthenticator controller See https://github.com/vmware-tanzu/pinniped/issues/260 for UX bummer. Signed-off-by: Andrew Keesler --- .../controller/authenticator/authenticator.go | 21 + .../authenticator/authncache/cache.go | 8 + .../cachecleaner/cachecleaner.go | 119 +++++ .../cachecleaner/cachecleaner_test.go | 207 +++++++++ .../jwtcachefiller/jwtcachefiller.go | 136 ++++++ .../jwtcachefiller/jwtcachefiller_test.go | 438 ++++++++++++++++++ .../webhookcachecleaner.go | 71 --- .../webhookcachecleaner_test.go | 144 ------ .../webhookcachefiller/webhookcachefiller.go | 11 +- .../controllermanager/prepare_controllers.go | 14 +- internal/registry/credentialrequest/rest.go | 2 +- .../registry/credentialrequest/rest_test.go | 2 +- test/integration/cli_test.go | 147 +++--- .../concierge_credentialrequest_test.go | 139 ++++-- test/library/client.go | 46 ++ 15 files changed, 1174 insertions(+), 331 deletions(-) create mode 100644 internal/controller/authenticator/authenticator.go create mode 100644 internal/controller/authenticator/cachecleaner/cachecleaner.go create mode 100644 internal/controller/authenticator/cachecleaner/cachecleaner_test.go create mode 100644 internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go create mode 100644 internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go delete mode 100644 internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner.go delete mode 100644 internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner_test.go diff --git a/internal/controller/authenticator/authenticator.go b/internal/controller/authenticator/authenticator.go new file mode 100644 index 00000000..16075ab5 --- /dev/null +++ b/internal/controller/authenticator/authenticator.go @@ -0,0 +1,21 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package authenticator contains helper code for dealing with *Authenticator CRDs. +package authenticator + +import ( + "encoding/base64" + + auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" +) + +// CABundle returns a PEM-encoded CA bundle from the provided spec. If the provided spec is nil, a +// nil CA bundle will be returned. If the provided spec contains a CA bundle that is not properly +// encoded, an error will be returned. +func CABundle(spec *auth1alpha1.TLSSpec) ([]byte, error) { + if spec == nil { + return nil, nil + } + return base64.StdEncoding.DecodeString(spec.CertificateAuthorityData) +} diff --git a/internal/controller/authenticator/authncache/cache.go b/internal/controller/authenticator/authncache/cache.go index 00de2e1e..5e8922e3 100644 --- a/internal/controller/authenticator/authncache/cache.go +++ b/internal/controller/authenticator/authncache/cache.go @@ -12,8 +12,10 @@ import ( "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/klog/v2" loginapi "go.pinniped.dev/generated/1.19/apis/concierge/login" + "go.pinniped.dev/internal/plog" ) var ( @@ -93,6 +95,12 @@ func (c *Cache) AuthenticateTokenCredentialRequest(ctx context.Context, req *log val := c.Get(key) if val == nil { + plog.Debug( + "authenticator does not exist", + "authenticator", klog.KRef(key.Namespace, key.Name), + "kind", key.Kind, + "apiGroup", key.APIGroup, + ) return nil, ErrNoSuchAuthenticator } diff --git a/internal/controller/authenticator/cachecleaner/cachecleaner.go b/internal/controller/authenticator/cachecleaner/cachecleaner.go new file mode 100644 index 00000000..ab762536 --- /dev/null +++ b/internal/controller/authenticator/cachecleaner/cachecleaner.go @@ -0,0 +1,119 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package cachecleaner implements a controller for garbage collecting authenticators from an authenticator cache. +package cachecleaner + +import ( + "fmt" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog/v2" + + auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + authinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1" + pinnipedcontroller "go.pinniped.dev/internal/controller" + "go.pinniped.dev/internal/controller/authenticator/authncache" + "go.pinniped.dev/internal/controllerlib" +) + +// closable is used to detect when a cache value has a Close() method on it. We use this to +// determine if we should call the Close() method on a cache value upon deleting it from the cache. +type closable interface { + Close() +} + +// New instantiates a new controllerlib.Controller which will garbage collect authenticators from the provided Cache. +func New( + cache *authncache.Cache, + webhooks authinformers.WebhookAuthenticatorInformer, + jwtAuthenticators authinformers.JWTAuthenticatorInformer, + log logr.Logger, +) controllerlib.Controller { + return controllerlib.New( + controllerlib.Config{ + Name: "cachecleaner-controller", + Syncer: &controller{ + cache: cache, + webhooks: webhooks, + jwtAuthenticators: jwtAuthenticators, + log: log.WithName("cachecleaner-controller"), + }, + }, + controllerlib.WithInformer( + webhooks, + pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()), + controllerlib.InformerOption{}, + ), + controllerlib.WithInformer( + jwtAuthenticators, + pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()), + controllerlib.InformerOption{}, + ), + ) +} + +type controller struct { + cache *authncache.Cache + webhooks authinformers.WebhookAuthenticatorInformer + jwtAuthenticators authinformers.JWTAuthenticatorInformer + log logr.Logger +} + +// Sync implements controllerlib.Syncer. +func (c *controller) Sync(_ controllerlib.Context) error { + webhooks, err := c.webhooks.Lister().List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list WebhookAuthenticators: %w", err) + } + + jwtAuthenticators, err := c.jwtAuthenticators.Lister().List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list JWTAuthenticators: %w", err) + } + + // Index the current authenticators by cache key. + authenticatorSet := map[authncache.Key]bool{} + for _, webhook := range webhooks { + key := authncache.Key{ + Namespace: webhook.Namespace, + Name: webhook.Name, + Kind: "WebhookAuthenticator", + APIGroup: auth1alpha1.SchemeGroupVersion.Group, + } + authenticatorSet[key] = true + } + for _, jwtAuthenticator := range jwtAuthenticators { + key := authncache.Key{ + Namespace: jwtAuthenticator.Namespace, + Name: jwtAuthenticator.Name, + Kind: "JWTAuthenticator", + APIGroup: auth1alpha1.SchemeGroupVersion.Group, + } + authenticatorSet[key] = true + } + + // Delete any entries from the cache which are no longer in the cluster. + for _, key := range c.cache.Keys() { + if key.APIGroup != auth1alpha1.SchemeGroupVersion.Group || (key.Kind != "WebhookAuthenticator" && key.Kind != "JWTAuthenticator") { + continue + } + if _, exists := authenticatorSet[key]; !exists { + c.log.WithValues( + "authenticator", + klog.KRef(key.Namespace, key.Name), + "kind", + key.Kind, + ).Info("deleting authenticator from cache") + + value := c.cache.Get(key) + if closable, ok := value.(closable); ok { + closable.Close() + } + + c.cache.Delete(key) + } + } + return nil +} diff --git a/internal/controller/authenticator/cachecleaner/cachecleaner_test.go b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go new file mode 100644 index 00000000..9e5cd147 --- /dev/null +++ b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go @@ -0,0 +1,207 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package cachecleaner + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + authv1alpha "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + pinnipedfake "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/fake" + pinnipedinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions" + "go.pinniped.dev/internal/controller/authenticator/authncache" + "go.pinniped.dev/internal/controllerlib" + "go.pinniped.dev/internal/testutil/testlogger" +) + +func TestController(t *testing.T) { + t.Parallel() + + testWebhookKey1 := authncache.Key{ + APIGroup: "authentication.concierge.pinniped.dev", + Kind: "WebhookAuthenticator", + Namespace: "test-namespace", + Name: "test-webhook-name-one", + } + testWebhookKey2 := authncache.Key{ + APIGroup: "authentication.concierge.pinniped.dev", + Kind: "WebhookAuthenticator", + Namespace: "test-namespace", + Name: "test-webhook-name-two", + } + testJWTAuthenticatorKey1 := authncache.Key{ + APIGroup: "authentication.concierge.pinniped.dev", + Kind: "JWTAuthenticator", + Namespace: "test-namespace", + Name: "test-jwt-authenticator-name-one", + } + testJWTAuthenticatorKey2 := authncache.Key{ + APIGroup: "authentication.concierge.pinniped.dev", + Kind: "JWTAuthenticator", + Namespace: "test-namespace", + Name: "test-jwt-authenticator-name-two", + } + testKeyUnknownType := authncache.Key{ + APIGroup: "authentication.concierge.pinniped.dev", + Kind: "SomeOtherAuthenticator", + Namespace: "test-namespace", + Name: "test-name-one", + } + + tests := []struct { + name string + objects []runtime.Object + initialCache map[authncache.Key]authncache.Value + wantErr string + wantLogs []string + wantCacheKeys []authncache.Key + }{ + { + name: "no change", + initialCache: map[authncache.Key]authncache.Value{ + testWebhookKey1: nil, + testJWTAuthenticatorKey1: nil, + }, + objects: []runtime.Object{ + &authv1alpha.WebhookAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testWebhookKey1.Namespace, + Name: testWebhookKey1.Name, + }, + }, + &authv1alpha.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testJWTAuthenticatorKey1.Namespace, + Name: testJWTAuthenticatorKey1.Name, + }, + }, + }, + wantCacheKeys: []authncache.Key{testWebhookKey1, testJWTAuthenticatorKey1}, + }, + { + name: "authenticators not yet added", + initialCache: nil, + objects: []runtime.Object{ + &authv1alpha.WebhookAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testWebhookKey1.Namespace, + Name: testWebhookKey1.Name, + }, + }, + &authv1alpha.WebhookAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testWebhookKey2.Namespace, + Name: testWebhookKey2.Name, + }, + }, + &authv1alpha.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testJWTAuthenticatorKey1.Namespace, + Name: testJWTAuthenticatorKey1.Name, + }, + }, + &authv1alpha.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testJWTAuthenticatorKey2.Namespace, + Name: testJWTAuthenticatorKey2.Name, + }, + }, + }, + wantCacheKeys: []authncache.Key{}, + }, + { + name: "successful cleanup", + initialCache: map[authncache.Key]authncache.Value{ + testWebhookKey1: nil, + testWebhookKey2: nil, + testJWTAuthenticatorKey1: newClosableCacheValue(t, "closable1", 0), + testJWTAuthenticatorKey2: newClosableCacheValue(t, "closable2", 1), + testKeyUnknownType: nil, + }, + objects: []runtime.Object{ + &authv1alpha.WebhookAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testWebhookKey1.Namespace, + Name: testWebhookKey1.Name, + }, + }, + &authv1alpha.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testJWTAuthenticatorKey1.Namespace, + Name: testJWTAuthenticatorKey1.Name, + }, + }, + }, + wantLogs: []string{ + `cachecleaner-controller "level"=0 "msg"="deleting authenticator from cache" "authenticator"={"name":"test-jwt-authenticator-name-two","namespace":"test-namespace"} "kind"="JWTAuthenticator"`, + `cachecleaner-controller "level"=0 "msg"="deleting authenticator from cache" "authenticator"={"name":"test-webhook-name-two","namespace":"test-namespace"} "kind"="WebhookAuthenticator"`, + }, + wantCacheKeys: []authncache.Key{testWebhookKey1, testJWTAuthenticatorKey1, testKeyUnknownType}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + // When we have t.Parallel() here, this test blocks pretty consistently...y tho? + + fakeClient := pinnipedfake.NewSimpleClientset(tt.objects...) + informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0) + cache := authncache.New() + for k, v := range tt.initialCache { + cache.Store(k, v) + } + testLog := testlogger.New(t) + + webhooks := informers.Authentication().V1alpha1().WebhookAuthenticators() + jwtAuthenticators := informers.Authentication().V1alpha1().JWTAuthenticators() + controller := New(cache, webhooks, jwtAuthenticators, testLog) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + informers.Start(ctx.Done()) + informers.WaitForCacheSync(ctx.Done()) + controllerlib.TestRunSynchronously(t, controller) + + syncCtx := controllerlib.Context{ + Context: ctx, + Key: controllerlib.Key{ + Namespace: "test-namespace", + Name: "test-webhook-name-one", + }, + } + + if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + require.ElementsMatch(t, tt.wantLogs, testLog.Lines()) + require.ElementsMatch(t, tt.wantCacheKeys, cache.Keys()) + }) + } +} + +func newClosableCacheValue(t *testing.T, name string, wantCloses int) authncache.Value { + t.Helper() + c := &closableCacheValue{} + t.Cleanup(func() { + require.Equalf(t, wantCloses, c.closeCallCount, "expected %s.Close() to be called %d times", name, wantCloses) + }) + return c +} + +type closableCacheValue struct { + authncache.Value + closeCallCount int +} + +func (c *closableCacheValue) Close() { + c.closeCallCount++ +} diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go new file mode 100644 index 00000000..3028fbf8 --- /dev/null +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -0,0 +1,136 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package jwtcachefiller implements a controller for filling an authncache.Cache with each +// added/updated JWTAuthenticator. +package jwtcachefiller + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/go-logr/logr" + "gopkg.in/square/go-jose.v2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" + "k8s.io/klog/v2" + + auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + authinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1" + pinnipedcontroller "go.pinniped.dev/internal/controller" + "go.pinniped.dev/internal/controller/authenticator" + "go.pinniped.dev/internal/controller/authenticator/authncache" + "go.pinniped.dev/internal/controllerlib" +) + +// 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 ( + defaultUsernameClaim = "sub" + defaultGroupsClaim = "groups" +) + +// defaultSupportedSigningAlgos returns the default signing algos that this JWTAuthenticator +// supports (i.e., if none are supplied by the user). +func defaultSupportedSigningAlgos() []string { + return []string{ + // RS256 is recommended by the OIDC spec and required, in some capacity. Since we want the + // JWTAuthenticator to be able to support many OIDC ID tokens out of the box, we include this + // algorithm by default. + string(jose.RS256), + // ES256 is what the Supervisor does, by default. We want integration with the JWTAuthenticator + // to be as seamless as possible, so we include this algorithm by default. + string(jose.ES256), + } +} + +// New instantiates a new controllerlib.Controller which will populate the provided authncache.Cache. +func New( + cache *authncache.Cache, + jwtAuthenticators authinformers.JWTAuthenticatorInformer, + log logr.Logger, +) controllerlib.Controller { + return controllerlib.New( + controllerlib.Config{ + Name: "jwtcachefiller-controller", + Syncer: &controller{ + cache: cache, + jwtAuthenticators: jwtAuthenticators, + log: log.WithName("jwtcachefiller-controller"), + }, + }, + controllerlib.WithInformer( + jwtAuthenticators, + pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct + controllerlib.InformerOption{}, + ), + ) +} + +type controller struct { + cache *authncache.Cache + jwtAuthenticators authinformers.JWTAuthenticatorInformer + log logr.Logger +} + +// Sync implements controllerlib.Syncer. +func (c *controller) Sync(ctx controllerlib.Context) error { + obj, err := c.jwtAuthenticators.Lister().JWTAuthenticators(ctx.Key.Namespace).Get(ctx.Key.Name) + if err != nil && errors.IsNotFound(err) { + c.log.Info("Sync() found that the JWTAuthenticator does not exist yet or was deleted") + return nil + } + if err != nil { + return fmt.Errorf("failed to get JWTAuthenticator %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err) + } + + jwtAuthenticator, err := newJWTAuthenticator(&obj.Spec) + if err != nil { + return fmt.Errorf("failed to build jwt authenticator: %w", err) + } + + c.cache.Store(authncache.Key{ + APIGroup: auth1alpha1.GroupName, + Kind: "JWTAuthenticator", + Namespace: ctx.Key.Namespace, + Name: ctx.Key.Name, + }, jwtAuthenticator) + c.log.WithValues("jwtAuthenticator", klog.KObj(obj), "issuer", obj.Spec.Issuer).Info("added new jwt authenticator") + return nil +} + +// newJWTAuthenticator creates a jwt authenticator from the provided spec. +func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*oidc.Authenticator, error) { + caBundle, err := authenticator.CABundle(spec.TLS) + if err != nil { + return nil, fmt.Errorf("invalid TLS configuration: %w", err) + } + + var caFile string + if caBundle != nil { + temp, err := ioutil.TempFile("", "pinniped-jwkauthenticator-cafile-*") + if err != nil { + return nil, fmt.Errorf("unable to create temporary file: %w", err) + } + + // We can safely remove the temp file at the end of this function since oidc.New() reads the + // provided CA file and then forgets about it. + defer func() { _ = os.Remove(temp.Name()) }() + + if _, err := temp.Write(caBundle); err != nil { + return nil, fmt.Errorf("cannot write CA file: %w", err) + } + + caFile = temp.Name() + } + + return oidc.New(oidc.Options{ + IssuerURL: spec.Issuer, + ClientID: spec.Audience, + UsernameClaim: defaultUsernameClaim, + GroupsClaim: defaultGroupsClaim, + SupportedSigningAlgs: defaultSupportedSigningAlgos(), + CAFile: caFile, + }) +} diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go new file mode 100644 index 00000000..c5d453f2 --- /dev/null +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -0,0 +1,438 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package jwtcachefiller + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "gopkg.in/square/go-jose.v2/jwt" + + "github.com/stretchr/testify/require" + "gopkg.in/square/go-jose.v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" + + auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" + pinnipedfake "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/fake" + pinnipedinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions" + "go.pinniped.dev/internal/controller/authenticator/authncache" + "go.pinniped.dev/internal/controllerlib" + "go.pinniped.dev/internal/testutil/testlogger" +) + +func TestController(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + syncKey controllerlib.Key + jwtAuthenticators []runtime.Object + wantErr string + wantLogs []string + wantCacheEntries int + }{ + { + name: "not found", + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="Sync() found that the JWTAuthenticator does not exist yet or was deleted"`, + }, + }, + { + name: "valid jwt authenticator with CA", + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, + }, + }, + }, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, + }, + wantCacheEntries: 1, + }, + { + name: "valid jwt authenticator without CA", + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + }, + }, + }, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, + }, + wantCacheEntries: 1, + }, + { + name: "invalid jwt authenticator CA", + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "not base64-encoded"}, + }, + }, + }, + wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 3", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + fakeClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...) + informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0) + cache := authncache.New() + testLog := testlogger.New(t) + + controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + informers.Start(ctx.Done()) + controllerlib.TestRunSynchronously(t, controller) + + syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey} + + if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantLogs, testLog.Lines()) + require.Equal(t, tt.wantCacheEntries, len(cache.Keys())) + }) + } +} + +func TestNewJWTAuthenticator(t *testing.T) { + t.Parallel() + + const ( + goodSubject = "some-subject" + goodAudience = "some-audience" + group0 = "some-group-0" + group1 = "some-group-1" + + goodECSigningKeyID = "some-ec-key-id" + goodRSASigningKeyID = "some-rsa-key-id" + ) + + goodECSigningKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + goodECSigningAlgo := jose.ES256 + require.NoError(t, err) + + goodRSASigningKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + goodRSASigningAlgo := jose.RS256 + + mux := http.NewServeMux() + server := httptest.NewTLSServer(mux) + t.Cleanup(server.Close) + + mux.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") + require.NoError(t, err) + })) + mux.Handle("/jwks.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ecJWK := jose.JSONWebKey{ + Key: goodECSigningKey, + KeyID: goodECSigningKeyID, + Algorithm: string(goodECSigningAlgo), + Use: "sig", + } + rsaJWK := jose.JSONWebKey{ + Key: goodRSASigningKey, + KeyID: goodRSASigningKeyID, + Algorithm: string(goodRSASigningAlgo), + Use: "sig", + } + jwks := jose.JSONWebKeySet{ + Keys: []jose.JSONWebKey{ecJWK.Public(), rsaJWK.Public()}, + } + require.NoError(t, json.NewEncoder(w).Encode(jwks)) + })) + + goodIssuer := server.URL + a, err := newJWTAuthenticator(&auth1alpha1.JWTAuthenticatorSpec{ + Issuer: goodIssuer, + Audience: goodAudience, + TLS: tlsSpecFromTLSConfig(server.TLS), + }) + require.NoError(t, err) + t.Cleanup(a.Close) + + // The implementation of AuthenticateToken() that we use waits 10 seconds after creation to + // perform OIDC discovery. Therefore, the JWTAuthenticator is not functional for the first 10 + // seconds. We sleep for 13 seconds in this unit test to give a little bit of cushion to that 10 + // second delay. + // + // We should get rid of this 10 second delay. See + // https://github.com/vmware-tanzu/pinniped/issues/260. + if testing.Short() { + t.Skip("skipping this test when '-short' flag is passed to avoid necessary 13 second sleep") + } + time.Sleep(time.Second * 13) + + var tests = []struct { + name string + jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{}) + jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) + wantResponse *authenticator.Response + wantAuthenticated bool + wantErrorRegexp string + }{ + { + name: "good token without groups and with EC signature", + wantResponse: &authenticator.Response{ + User: &user.DefaultInfo{ + Name: goodSubject, + }, + }, + wantAuthenticated: true, + }, + { + name: "good token without groups and with RSA signature", + jwtSignature: func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) { + *key = goodRSASigningKey + *algo = goodRSASigningAlgo + *kid = goodRSASigningKeyID + }, + wantResponse: &authenticator.Response{ + User: &user.DefaultInfo{ + Name: goodSubject, + }, + }, + wantAuthenticated: true, + }, + { + name: "good token with groups as array", + jwtClaims: func(_ *jwt.Claims, groups *interface{}) { + *groups = []string{group0, group1} + }, + wantResponse: &authenticator.Response{ + User: &user.DefaultInfo{ + Name: goodSubject, + Groups: []string{group0, group1}, + }, + }, + wantAuthenticated: true, + }, + { + name: "good token with groups as string", + jwtClaims: func(_ *jwt.Claims, groups *interface{}) { + *groups = group0 + }, + wantResponse: &authenticator.Response{ + User: &user.DefaultInfo{ + Name: goodSubject, + Groups: []string{group0}, + }, + }, + wantAuthenticated: true, + }, + { + name: "good token with nbf unset", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.NotBefore = nil + }, + wantResponse: &authenticator.Response{ + User: &user.DefaultInfo{ + Name: goodSubject, + }, + }, + wantAuthenticated: true, + }, + { + name: "bad token with groups as map", + jwtClaims: func(_ *jwt.Claims, groups *interface{}) { + *groups = map[string]string{"not an array": "or a string"} + }, + wantErrorRegexp: "oidc: parse groups claim \"groups\": json: cannot unmarshal object into Go value of type string", + }, + { + name: "bad token with wrong issuer", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.Issuer = "wrong-issuer" + }, + wantResponse: nil, + wantAuthenticated: false, + }, + { + name: "bad token with no audience", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.Audience = nil + }, + wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \[\]`, + }, + { + name: "bad token with wrong audience", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.Audience = []string{"wrong-audience"} + }, + wantErrorRegexp: `oidc: verify token: oidc: expected audience "some-audience" got \["wrong-audience"\]`, + }, + { + name: "bad token with nbf in the future", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.NotBefore = jwt.NewNumericDate(time.Date(3020, 2, 3, 4, 5, 6, 7, time.UTC)) + }, + wantErrorRegexp: `oidc: verify token: oidc: current time .* before the nbf \(not before\) time: 3020-.*`, + }, + { + name: "bad token with exp in past", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)) + }, + wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-02-02 23:09:04 -0456 LMT\)`, + }, + { + name: "bad token without exp", + jwtClaims: func(claims *jwt.Claims, _ *interface{}) { + claims.Expiry = nil + }, + wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-01-01 00:00:00 \+0000 UTC\)`, + }, + { + name: "signing key is wrong", + jwtSignature: func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) { + var err error + *key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + *algo = jose.ES256 + }, + wantErrorRegexp: `oidc: verify token: failed to verify signature: failed to verify id token signature`, + }, + { + name: "signing algo is unsupported", + jwtSignature: func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string) { + var err error + *key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(t, err) + *algo = jose.ES384 + }, + wantErrorRegexp: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`, + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + wellKnownClaims := jwt.Claims{ + Issuer: goodIssuer, + Subject: goodSubject, + 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)), + } + var groups interface{} + if test.jwtClaims != nil { + test.jwtClaims(&wellKnownClaims, &groups) + } + + var signingKey interface{} = goodECSigningKey + signingAlgo := goodECSigningAlgo + signingKID := goodECSigningKeyID + if test.jwtSignature != nil { + test.jwtSignature(&signingKey, &signingAlgo, &signingKID) + } + + jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups) + rsp, authenticated, err := a.AuthenticateToken(context.Background(), jwt) + if test.wantErrorRegexp != "" { + require.Error(t, err) + require.Regexp(t, test.wantErrorRegexp, err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, test.wantResponse, rsp) + require.Equal(t, test.wantAuthenticated, authenticated) + } + }) + } +} + +func tlsSpecFromTLSConfig(tls *tls.Config) *auth1alpha1.TLSSpec { + pemData := make([]byte, 0) + for _, certificate := range tls.Certificates { + for _, reallyCertificate := range certificate.Certificate { + pemData = append(pemData, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: reallyCertificate, + })...) + } + } + return &auth1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString(pemData), + } +} + +func createJWT( + t *testing.T, + signingKey interface{}, + signingAlgo jose.SignatureAlgorithm, + kid string, + claims *jwt.Claims, + groups interface{}, +) string { + t.Helper() + + sig, err := jose.NewSigner( + jose.SigningKey{Algorithm: signingAlgo, Key: signingKey}, + (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", kid), + ) + require.NoError(t, err) + + builder := jwt.Signed(sig).Claims(claims) + if groups != nil { + builder = builder.Claims(map[string]interface{}{"groups": groups}) + } + jwt, err := builder.CompactSerialize() + require.NoError(t, err) + + t.Log("andrew:", jwt) + + return jwt +} diff --git a/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner.go b/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner.go deleted file mode 100644 index b8d28acf..00000000 --- a/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2020 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package webhookcachecleaner implements a controller for garbage collecting webhook authenticators from an authenticator cache. -package webhookcachecleaner - -import ( - "fmt" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/klog/v2" - - auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" - authinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1" - pinnipedcontroller "go.pinniped.dev/internal/controller" - "go.pinniped.dev/internal/controller/authenticator/authncache" - "go.pinniped.dev/internal/controllerlib" -) - -// New instantiates a new controllerlib.Controller which will garbage collect webhooks from the provided Cache. -func New(cache *authncache.Cache, webhooks authinformers.WebhookAuthenticatorInformer, log logr.Logger) controllerlib.Controller { - return controllerlib.New( - controllerlib.Config{ - Name: "webhookcachecleaner-controller", - Syncer: &controller{ - cache: cache, - webhooks: webhooks, - log: log.WithName("webhookcachecleaner-controller"), - }, - }, - controllerlib.WithInformer( - webhooks, - pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()), - controllerlib.InformerOption{}, - ), - ) -} - -type controller struct { - cache *authncache.Cache - webhooks authinformers.WebhookAuthenticatorInformer - log logr.Logger -} - -// Sync implements controllerlib.Syncer. -func (c *controller) Sync(_ controllerlib.Context) error { - webhooks, err := c.webhooks.Lister().List(labels.Everything()) - if err != nil { - return fmt.Errorf("failed to list WebhookAuthenticators: %w", err) - } - - // Index the current webhooks by key. - webhooksByKey := map[controllerlib.Key]*auth1alpha1.WebhookAuthenticator{} - for _, webhook := range webhooks { - key := controllerlib.Key{Namespace: webhook.Namespace, Name: webhook.Name} - webhooksByKey[key] = webhook - } - - // Delete any entries from the cache which are no longer in the cluster. - for _, key := range c.cache.Keys() { - if key.APIGroup != auth1alpha1.SchemeGroupVersion.Group || key.Kind != "WebhookAuthenticator" { - continue - } - if _, exists := webhooksByKey[controllerlib.Key{Namespace: key.Namespace, Name: key.Name}]; !exists { - c.log.WithValues("webhook", klog.KRef(key.Namespace, key.Name)).Info("deleting webhook authenticator from cache") - c.cache.Delete(key) - } - } - return nil -} diff --git a/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner_test.go b/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner_test.go deleted file mode 100644 index 5f1d0674..00000000 --- a/internal/controller/authenticator/webhookcachecleaner/webhookcachecleaner_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020 the Pinniped contributors. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package webhookcachecleaner - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - authv1alpha "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" - pinnipedfake "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/fake" - pinnipedinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions" - "go.pinniped.dev/internal/controller/authenticator/authncache" - "go.pinniped.dev/internal/controllerlib" - "go.pinniped.dev/internal/testutil/testlogger" -) - -func TestController(t *testing.T) { - t.Parallel() - - testKey1 := authncache.Key{ - APIGroup: "authentication.concierge.pinniped.dev", - Kind: "WebhookAuthenticator", - Namespace: "test-namespace", - Name: "test-name-one", - } - testKey2 := authncache.Key{ - APIGroup: "authentication.concierge.pinniped.dev", - Kind: "WebhookAuthenticator", - Namespace: "test-namespace", - Name: "test-name-two", - } - testKeyNonwebhook := authncache.Key{ - APIGroup: "authentication.concierge.pinniped.dev", - Kind: "SomeOtherAuthenticator", - Namespace: "test-namespace", - Name: "test-name-one", - } - - tests := []struct { - name string - webhooks []runtime.Object - initialCache map[authncache.Key]authncache.Value - wantErr string - wantLogs []string - wantCacheKeys []authncache.Key - }{ - { - name: "no change", - initialCache: map[authncache.Key]authncache.Value{testKey1: nil}, - webhooks: []runtime.Object{ - &authv1alpha.WebhookAuthenticator{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testKey1.Namespace, - Name: testKey1.Name, - }, - }, - }, - wantCacheKeys: []authncache.Key{testKey1}, - }, - { - name: "authenticators not yet added", - initialCache: nil, - webhooks: []runtime.Object{ - &authv1alpha.WebhookAuthenticator{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testKey1.Namespace, - Name: testKey1.Name, - }, - }, - &authv1alpha.WebhookAuthenticator{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testKey2.Namespace, - Name: testKey2.Name, - }, - }, - }, - wantCacheKeys: []authncache.Key{}, - }, - { - name: "successful cleanup", - initialCache: map[authncache.Key]authncache.Value{ - testKey1: nil, - testKey2: nil, - testKeyNonwebhook: nil, - }, - webhooks: []runtime.Object{ - &authv1alpha.WebhookAuthenticator{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testKey1.Namespace, - Name: testKey1.Name, - }, - }, - }, - wantLogs: []string{ - `webhookcachecleaner-controller "level"=0 "msg"="deleting webhook authenticator from cache" "webhook"={"name":"test-name-two","namespace":"test-namespace"}`, - }, - wantCacheKeys: []authncache.Key{testKey1, testKeyNonwebhook}, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - fakeClient := pinnipedfake.NewSimpleClientset(tt.webhooks...) - informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0) - cache := authncache.New() - for k, v := range tt.initialCache { - cache.Store(k, v) - } - testLog := testlogger.New(t) - - controller := New(cache, informers.Authentication().V1alpha1().WebhookAuthenticators(), testLog) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - informers.Start(ctx.Done()) - controllerlib.TestRunSynchronously(t, controller) - - syncCtx := controllerlib.Context{ - Context: ctx, - Key: controllerlib.Key{ - Namespace: "test-namespace", - Name: "test-name-one", - }, - } - - if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" { - require.EqualError(t, err, tt.wantErr) - } else { - require.NoError(t, err) - } - require.Equal(t, tt.wantLogs, testLog.Lines()) - require.ElementsMatch(t, tt.wantCacheKeys, cache.Keys()) - }) - } -} diff --git a/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go b/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go index 9dcf0447..34330fcf 100644 --- a/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go +++ b/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go @@ -5,7 +5,6 @@ package webhookcachefiller import ( - "encoding/base64" "fmt" "io/ioutil" "os" @@ -23,6 +22,7 @@ import ( auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" authinformers "go.pinniped.dev/generated/1.19/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/controllerlib" ) @@ -92,7 +92,7 @@ func newWebhookAuthenticator( defer func() { _ = os.Remove(temp.Name()) }() cluster := &clientcmdapi.Cluster{Server: spec.Endpoint} - cluster.CertificateAuthorityData, err = getCABundle(spec.TLS) + cluster.CertificateAuthorityData, err = pinnipedauthenticator.CABundle(spec.TLS) if err != nil { return nil, fmt.Errorf("invalid TLS configuration: %w", err) } @@ -121,10 +121,3 @@ func newWebhookAuthenticator( return webhook.New(temp.Name(), version, implicitAuds, customDial) } - -func getCABundle(spec *auth1alpha1.TLSSpec) ([]byte, error) { - if spec == nil { - return nil, nil - } - return base64.StdEncoding.DecodeString(spec.CertificateAuthorityData) -} diff --git a/internal/controllermanager/prepare_controllers.go b/internal/controllermanager/prepare_controllers.go index bb8c3523..22ce266d 100644 --- a/internal/controllermanager/prepare_controllers.go +++ b/internal/controllermanager/prepare_controllers.go @@ -25,7 +25,8 @@ import ( "go.pinniped.dev/internal/config/concierge" "go.pinniped.dev/internal/controller/apicerts" "go.pinniped.dev/internal/controller/authenticator/authncache" - "go.pinniped.dev/internal/controller/authenticator/webhookcachecleaner" + "go.pinniped.dev/internal/controller/authenticator/cachecleaner" + "go.pinniped.dev/internal/controller/authenticator/jwtcachefiller" "go.pinniped.dev/internal/controller/authenticator/webhookcachefiller" "go.pinniped.dev/internal/controller/issuerconfig" "go.pinniped.dev/internal/controller/kubecertagent" @@ -238,9 +239,18 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) { singletonWorker, ). WithController( - webhookcachecleaner.New( + jwtcachefiller.New( + c.AuthenticatorCache, + informers.installationNamespacePinniped.Authentication().V1alpha1().JWTAuthenticators(), + klogr.New(), + ), + singletonWorker, + ). + WithController( + cachecleaner.New( c.AuthenticatorCache, informers.installationNamespacePinniped.Authentication().V1alpha1().WebhookAuthenticators(), + informers.installationNamespacePinniped.Authentication().V1alpha1().JWTAuthenticators(), klogr.New(), ), singletonWorker, diff --git a/internal/registry/credentialrequest/rest.go b/internal/registry/credentialrequest/rest.go index fcbb0516..3e2e6eb3 100644 --- a/internal/registry/credentialrequest/rest.go +++ b/internal/registry/credentialrequest/rest.go @@ -79,7 +79,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation user, err := r.authenticator.AuthenticateTokenCredentialRequest(ctx, credentialRequest) if err != nil { - traceFailureWithError(t, "webhook authentication", err) + traceFailureWithError(t, "token authentication", err) return failureResponse(), nil } if user == nil || user.GetName() == "" { diff --git a/internal/registry/credentialrequest/rest_test.go b/internal/registry/credentialrequest/rest_test.go index 4b311715..032dc681 100644 --- a/internal/registry/credentialrequest/rest_test.go +++ b/internal/registry/credentialrequest/rest_test.go @@ -145,7 +145,7 @@ func TestCreate(t *testing.T) { response, err := callCreate(context.Background(), storage, req) requireSuccessfulResponseWithAuthenticationFailureMessage(t, err, response) - requireOneLogStatement(r, logger, `"failure" failureType:webhook authentication,msg:some webhook error`) + requireOneLogStatement(r, logger, `"failure" failureType:token authentication,msg:some webhook error`) }) it("CreateSucceedsWithAnUnauthenticatedStatusWhenWebhookReturnsAnEmptyUsername", func() { diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index cc51904e..707fe0e7 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -114,17 +114,93 @@ func TestCLILoginOIDC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Start the browser driver. - page := browsertest.Open(t) - // Build pinniped CLI. t.Logf("building CLI binary") pinnipedExe := buildPinnipedCLI(t) + // Run "pinniped login oidc" to get an ExecCredential struct with an OIDC ID token. + credOutput, sessionCachePath := runPinniedLoginOIDC(ctx, t, pinnipedExe) + + // Assert some properties of the ExecCredential. + t.Logf("validating ExecCredential") + require.NotNil(t, credOutput.Status) + require.Empty(t, credOutput.Status.ClientKeyData) + require.Empty(t, credOutput.Status.ClientCertificateData) + + // There should be at least 1 minute of remaining expiration (probably more). + require.NotNil(t, credOutput.Status.ExpirationTimestamp) + ttl := time.Until(credOutput.Status.ExpirationTimestamp.Time) + require.Greater(t, ttl.Milliseconds(), (1 * time.Minute).Milliseconds()) + + // Assert some properties about the token, which should be a valid JWT. + require.NotEmpty(t, credOutput.Status.Token) + jws, err := jose.ParseSigned(credOutput.Status.Token) + require.NoError(t, err) + claims := map[string]interface{}{} + require.NoError(t, json.Unmarshal(jws.UnsafePayloadWithoutVerification(), &claims)) + require.Equal(t, env.CLITestUpstream.Issuer, claims["iss"]) + require.Equal(t, env.CLITestUpstream.ClientID, claims["aud"]) + require.Equal(t, env.CLITestUpstream.Username, claims["email"]) + require.NotEmpty(t, claims["nonce"]) + + // Run the CLI again with the same session cache and login parameters. + t.Logf("starting second CLI subprocess to test session caching") + cmd2Output, err := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath).CombinedOutput() + require.NoError(t, err, string(cmd2Output)) + + // Expect the CLI to output the same ExecCredential in JSON format. + t.Logf("validating second ExecCredential") + var credOutput2 clientauthenticationv1beta1.ExecCredential + require.NoErrorf(t, json.Unmarshal(cmd2Output, &credOutput2), + "command returned something other than an ExecCredential:\n%s", string(cmd2Output)) + require.Equal(t, credOutput, credOutput2) + + // Overwrite the cache entry to remove the access and ID tokens. + t.Logf("overwriting cache to remove valid ID token") + cache := filesession.New(sessionCachePath) + cacheKey := oidcclient.SessionCacheKey{ + Issuer: env.CLITestUpstream.Issuer, + ClientID: env.CLITestUpstream.ClientID, + Scopes: []string{"email", "offline_access", "openid", "profile"}, + RedirectURI: strings.ReplaceAll(env.CLITestUpstream.CallbackURL, "127.0.0.1", "localhost"), + } + cached := cache.GetToken(cacheKey) + require.NotNil(t, cached) + require.NotNil(t, cached.RefreshToken) + require.NotEmpty(t, cached.RefreshToken.Token) + cached.IDToken = nil + cached.AccessToken = nil + cache.PutToken(cacheKey, cached) + + // Run the CLI a third time with the same session cache and login parameters. + t.Logf("starting third CLI subprocess to test refresh flow") + cmd3Output, err := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath).CombinedOutput() + require.NoError(t, err, string(cmd2Output)) + + // Expect the CLI to output a new ExecCredential in JSON format (different from the one returned the first two times). + t.Logf("validating third ExecCredential") + var credOutput3 clientauthenticationv1beta1.ExecCredential + require.NoErrorf(t, json.Unmarshal(cmd3Output, &credOutput3), + "command returned something other than an ExecCredential:\n%s", string(cmd2Output)) + require.NotEqual(t, credOutput2.Status.Token, credOutput3.Status.Token) +} + +func runPinniedLoginOIDC( + ctx context.Context, + t *testing.T, + pinnipedExe string, +) (clientauthenticationv1beta1.ExecCredential, string) { + t.Helper() + + env := library.IntegrationEnv(t) + // Make a temp directory to hold the session cache for this test. sessionCachePath := testutil.TempDir(t) + "/sessions.yaml" - // Start the CLI running the "alpha login oidc [...]" command with stdout/stderr connected to pipes. + // Start the browser driver. + page := browsertest.Open(t) + + // Start the CLI running the "login oidc [...]" command with stdout/stderr connected to pipes. cmd := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath) stderr, err := cmd.StderrPipe() require.NoError(t, err) @@ -221,68 +297,7 @@ func TestCLILoginOIDC(t *testing.T) { case credOutput = <-credOutputChan: } - // Assert some properties of the ExecCredential. - t.Logf("validating ExecCredential") - require.NotNil(t, credOutput.Status) - require.Empty(t, credOutput.Status.ClientKeyData) - require.Empty(t, credOutput.Status.ClientCertificateData) - - // There should be at least 1 minute of remaining expiration (probably more). - require.NotNil(t, credOutput.Status.ExpirationTimestamp) - ttl := time.Until(credOutput.Status.ExpirationTimestamp.Time) - require.Greater(t, ttl.Milliseconds(), (1 * time.Minute).Milliseconds()) - - // Assert some properties about the token, which should be a valid JWT. - require.NotEmpty(t, credOutput.Status.Token) - jws, err := jose.ParseSigned(credOutput.Status.Token) - require.NoError(t, err) - claims := map[string]interface{}{} - require.NoError(t, json.Unmarshal(jws.UnsafePayloadWithoutVerification(), &claims)) - require.Equal(t, env.CLITestUpstream.Issuer, claims["iss"]) - require.Equal(t, env.CLITestUpstream.ClientID, claims["aud"]) - require.Equal(t, env.CLITestUpstream.Username, claims["email"]) - require.NotEmpty(t, claims["nonce"]) - - // Run the CLI again with the same session cache and login parameters. - t.Logf("starting second CLI subprocess to test session caching") - cmd2Output, err := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath).CombinedOutput() - require.NoError(t, err, string(cmd2Output)) - - // Expect the CLI to output the same ExecCredential in JSON format. - t.Logf("validating second ExecCredential") - var credOutput2 clientauthenticationv1beta1.ExecCredential - require.NoErrorf(t, json.Unmarshal(cmd2Output, &credOutput2), - "command returned something other than an ExecCredential:\n%s", string(cmd2Output)) - require.Equal(t, credOutput, credOutput2) - - // Overwrite the cache entry to remove the access and ID tokens. - t.Logf("overwriting cache to remove valid ID token") - cache := filesession.New(sessionCachePath) - cacheKey := oidcclient.SessionCacheKey{ - Issuer: env.CLITestUpstream.Issuer, - ClientID: env.CLITestUpstream.ClientID, - Scopes: []string{"email", "offline_access", "openid", "profile"}, - RedirectURI: strings.ReplaceAll(env.CLITestUpstream.CallbackURL, "127.0.0.1", "localhost"), - } - cached := cache.GetToken(cacheKey) - require.NotNil(t, cached) - require.NotNil(t, cached.RefreshToken) - require.NotEmpty(t, cached.RefreshToken.Token) - cached.IDToken = nil - cached.AccessToken = nil - cache.PutToken(cacheKey, cached) - - // Run the CLI a third time with the same session cache and login parameters. - t.Logf("starting third CLI subprocess to test refresh flow") - cmd3Output, err := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath).CombinedOutput() - require.NoError(t, err, string(cmd2Output)) - - // Expect the CLI to output a new ExecCredential in JSON format (different from the one returned the first two times). - t.Logf("validating third ExecCredential") - var credOutput3 clientauthenticationv1beta1.ExecCredential - require.NoErrorf(t, json.Unmarshal(cmd3Output, &credOutput3), - "command returned something other than an ExecCredential:\n%s", string(cmd2Output)) - require.NotEqual(t, credOutput2.Status.Token, credOutput3.Status.Token) + return credOutput, sessionCachePath } func readAndExpectEmpty(r io.Reader) (err error) { diff --git a/test/integration/concierge_credentialrequest_test.go b/test/integration/concierge_credentialrequest_test.go index 30ca7eb3..5e2458e0 100644 --- a/test/integration/concierge_credentialrequest_test.go +++ b/test/integration/concierge_credentialrequest_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + jwtpkg "gopkg.in/square/go-jose.v2/jwt" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -44,48 +45,89 @@ func TestSuccessfulCredentialRequest(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute) defer cancel() - testWebhook := library.CreateTestWebhookAuthenticator(ctx, t) + tests := []struct { + name string + authenticator func(t *testing.T) corev1.TypedLocalObjectReference + token func(t *testing.T) (token string, username string, groups []string) + }{ + { + name: "webhook", + authenticator: func(t *testing.T) corev1.TypedLocalObjectReference { + return library.CreateTestWebhookAuthenticator(ctx, t) + }, + token: func(t *testing.T) (string, string, []string) { + return library.IntegrationEnv(t).TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups + }, + }, + { + name: "jwt authenticator", + authenticator: func(t *testing.T) corev1.TypedLocalObjectReference { + return library.CreateTestJWTAuthenticator(ctx, t) + }, + token: func(t *testing.T) (string, string, []string) { + pinnipedExe := buildPinnipedCLI(t) + credOutput, _ := runPinniedLoginOIDC(ctx, t, pinnipedExe) + token := credOutput.Status.Token - var response *loginv1alpha1.TokenCredentialRequest - successfulResponse := func() bool { - var err error - response, err = makeRequest(ctx, t, validCredentialRequestSpecWithRealToken(t, testWebhook)) - require.NoError(t, err, "the request should never fail at the HTTP level") - return response.Status.Credential != nil + // By default, the JWTAuthenticator expects the username to be in the "sub" claim and the + // groups to be in the "groups" claim. + username, groups := getJWTSubAndGroupsClaims(t, token) + + return credOutput.Status.Token, username, groups + }, + }, } - assert.Eventually(t, successfulResponse, 10*time.Second, 500*time.Millisecond) - require.NotNil(t, response) - require.NotNil(t, response.Status.Credential) - require.Empty(t, response.Status.Message) - require.Empty(t, response.Spec) - require.Empty(t, response.Status.Credential.Token) - require.NotEmpty(t, response.Status.Credential.ClientCertificateData) - require.Equal(t, env.TestUser.ExpectedUsername, getCommonName(t, response.Status.Credential.ClientCertificateData)) - require.ElementsMatch(t, env.TestUser.ExpectedGroups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) - require.NotEmpty(t, response.Status.Credential.ClientKeyData) - require.NotNil(t, response.Status.Credential.ExpirationTimestamp) - require.InDelta(t, 5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute)) + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + authenticator := test.authenticator(t) + token, username, groups := test.token(t) - // Create a client using the admin kubeconfig. - adminClient := library.NewClientset(t) + var response *loginv1alpha1.TokenCredentialRequest + successfulResponse := func() bool { + var err error + response, err = makeRequest(ctx, t, loginv1alpha1.TokenCredentialRequestSpec{ + Token: token, + Authenticator: authenticator, + }) + require.NoError(t, err, "the request should never fail at the HTTP level") + return response.Status.Credential != nil + } + assert.Eventually(t, successfulResponse, 10*time.Second, 500*time.Millisecond) + require.NotNil(t, response) + require.Emptyf(t, response.Status.Message, "value is: %q", safeDerefStringPtr(response.Status.Message)) + require.NotNil(t, response.Status.Credential) + require.Empty(t, response.Spec) + require.Empty(t, response.Status.Credential.Token) + require.NotEmpty(t, response.Status.Credential.ClientCertificateData) + require.Equal(t, username, getCommonName(t, response.Status.Credential.ClientCertificateData)) + require.ElementsMatch(t, groups, getOrganizations(t, response.Status.Credential.ClientCertificateData)) + require.NotEmpty(t, response.Status.Credential.ClientKeyData) + require.NotNil(t, response.Status.Credential.ExpirationTimestamp) + require.InDelta(t, 5*time.Minute, time.Until(response.Status.Credential.ExpirationTimestamp.Time), float64(time.Minute)) - // Create a client using the certificate from the CredentialRequest. - clientWithCertFromCredentialRequest := library.NewClientsetWithCertAndKey( - t, - response.Status.Credential.ClientCertificateData, - response.Status.Credential.ClientKeyData, - ) + // Create a client using the admin kubeconfig. + adminClient := library.NewClientset(t) - t.Run( - "access as user", - library.AccessAsUserTest(ctx, adminClient, env.TestUser.ExpectedUsername, clientWithCertFromCredentialRequest), - ) - for _, group := range env.TestUser.ExpectedGroups { - group := group - t.Run( - "access as group "+group, - library.AccessAsGroupTest(ctx, adminClient, group, clientWithCertFromCredentialRequest), - ) + // Create a client using the certificate from the CredentialRequest. + clientWithCertFromCredentialRequest := library.NewClientsetWithCertAndKey( + t, + response.Status.Credential.ClientCertificateData, + response.Status.Credential.ClientKeyData, + ) + + t.Run( + "access as user", + library.AccessAsUserTest(ctx, adminClient, username, clientWithCertFromCredentialRequest), + ) + for _, group := range groups { + group := group + t.Run( + "access as group "+group, + library.AccessAsGroupTest(ctx, adminClient, group, clientWithCertFromCredentialRequest), + ) + } + }) } } @@ -183,3 +225,26 @@ func getOrganizations(t *testing.T, certPEM string) []string { return cert.Subject.Organization } + +func safeDerefStringPtr(s *string) string { + if s == nil { + return "" + } + return *s +} + +func getJWTSubAndGroupsClaims(t *testing.T, jwt string) (string, []string) { + t.Helper() + + token, err := jwtpkg.ParseSigned(jwt) + require.NoError(t, err) + + var claims struct { + Sub string `json:"sub"` + Groups []string `json:"groups"` + } + err = token.UnsafeClaimsWithoutVerification(&claims) + require.NoError(t, err) + + return claims.Sub, claims.Groups +} diff --git a/test/library/client.go b/test/library/client.go index b151b86b..7a24b54d 100644 --- a/test/library/client.go +++ b/test/library/client.go @@ -6,6 +6,7 @@ package library import ( "context" "crypto/rand" + "encoding/base64" "encoding/hex" "fmt" "io" @@ -163,6 +164,51 @@ func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.Ty } } +// CreateTestJWTAuthenticator creates and returns a test JWTAuthenticator in +// $PINNIPED_TEST_CONCIERGE_NAMESPACE, which will be automatically deleted at the end of the current +// test's lifetime. It returns a corev1.TypedLocalObjectReference which describes the test JWT +// authenticator within the test namespace. +// +// CreateTestJWTAuthenticator gets the OIDC issuer info from IntegrationEnv().CLITestUpstream. +func CreateTestJWTAuthenticator(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference { + t.Helper() + testEnv := IntegrationEnv(t) + + client := NewConciergeClientset(t) + jwtAuthenticators := client.AuthenticationV1alpha1().JWTAuthenticators(testEnv.ConciergeNamespace) + + createContext, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + jwtAuthenticator, err := jwtAuthenticators.Create(createContext, &auth1alpha1.JWTAuthenticator{ + ObjectMeta: testObjectMeta(t, "jwt-authenticator"), + Spec: auth1alpha1.JWTAuthenticatorSpec{ + Issuer: testEnv.CLITestUpstream.Issuer, + Audience: testEnv.CLITestUpstream.ClientID, + TLS: &auth1alpha1.TLSSpec{ + CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(testEnv.CLITestUpstream.CABundle)), + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err, "could not create test JWTAuthenticator") + t.Logf("created test JWTAuthenticator %s/%s", jwtAuthenticator.Namespace, jwtAuthenticator.Name) + + t.Cleanup(func() { + t.Helper() + t.Logf("cleaning up test JWTAuthenticator %s/%s", jwtAuthenticator.Namespace, jwtAuthenticator.Name) + deleteCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := jwtAuthenticators.Delete(deleteCtx, jwtAuthenticator.Name, metav1.DeleteOptions{}) + require.NoErrorf(t, err, "could not cleanup test JWTAuthenticator %s/%s", jwtAuthenticator.Namespace, jwtAuthenticator.Name) + }) + + return corev1.TypedLocalObjectReference{ + APIGroup: &auth1alpha1.SchemeGroupVersion.Group, + Kind: "JWTAuthenticator", + Name: jwtAuthenticator.Name, + } +} + // CreateTestOIDCProvider creates and returns a test OIDCProvider in // $PINNIPED_TEST_SUPERVISOR_NAMESPACE, which will be automatically deleted at the end of the // current test's lifetime. It generates a random, valid, issuer for the OIDCProvider. From 0efc19a1b766616a22c3b0586df94f57a0f787a8 Mon Sep 17 00:00:00 2001 From: Andrew Keesler Date: Mon, 7 Dec 2020 20:40:20 -0500 Subject: [PATCH 3/6] Support JWTAuthenticator in pinniped CLI Signed-off-by: Andrew Keesler --- cmd/pinniped/cmd/exchange_credential.go | 7 ++- cmd/pinniped/cmd/exchange_credential_test.go | 50 +++++++++++++++++++- cmd/pinniped/cmd/get_kubeconfig.go | 2 +- cmd/pinniped/cmd/get_kubeconfig_test.go | 4 +- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/cmd/pinniped/cmd/exchange_credential.go b/cmd/pinniped/cmd/exchange_credential.go index 2440864c..74d1176b 100644 --- a/cmd/pinniped/cmd/exchange_credential.go +++ b/cmd/pinniped/cmd/exchange_credential.go @@ -64,7 +64,7 @@ func newExchangeCredentialCmd(args []string, stdout, stderr io.Writer) *exchange - PINNIPED_NAMESPACE: the namespace of the authenticator to authenticate against - PINNIPED_AUTHENTICATOR_TYPE: the type of authenticator to authenticate - against (e.g., "webhook") + against (e.g., "webhook", "jwt") - PINNIPED_AUTHENTICATOR_NAME: the name of the authenticator to authenticate against - PINNIPED_CA_BUNDLE: the CA bundle to trust when calling @@ -148,8 +148,11 @@ func exchangeCredential(envGetter envGetter, tokenExchanger tokenExchanger, outp case "webhook": authenticator.APIGroup = &auth1alpha1.SchemeGroupVersion.Group authenticator.Kind = "WebhookAuthenticator" + case "jwt": + authenticator.APIGroup = &auth1alpha1.SchemeGroupVersion.Group + authenticator.Kind = "JWTAuthenticator" default: - return fmt.Errorf(`%w: %q, supported values are "webhook"`, ErrInvalidAuthenticatorType, authenticatorType) + return fmt.Errorf(`%w: %q, supported values are "webhook" and "jwt"`, ErrInvalidAuthenticatorType, authenticatorType) } cred, err := tokenExchanger(ctx, namespace, authenticator, token, caBundle, apiEndpoint) diff --git a/cmd/pinniped/cmd/exchange_credential_test.go b/cmd/pinniped/cmd/exchange_credential_test.go index 2b1367c2..babd4f3b 100644 --- a/cmd/pinniped/cmd/exchange_credential_test.go +++ b/cmd/pinniped/cmd/exchange_credential_test.go @@ -18,6 +18,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" + auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" "go.pinniped.dev/internal/here" "go.pinniped.dev/internal/testutil" ) @@ -46,7 +47,7 @@ var ( - PINNIPED_NAMESPACE: the namespace of the authenticator to authenticate against - PINNIPED_AUTHENTICATOR_TYPE: the type of authenticator to authenticate - against (e.g., "webhook") + against (e.g., "webhook", "jwt") - PINNIPED_AUTHENTICATOR_NAME: the name of the authenticator to authenticate against - PINNIPED_CA_BUNDLE: the CA bundle to trust when calling @@ -193,7 +194,7 @@ func TestExchangeCredential(t *testing.T) { it("returns an error when PINNIPED_AUTHENTICATOR_TYPE is missing", func() { fakeEnv["PINNIPED_AUTHENTICATOR_TYPE"] = "invalid" err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) - r.EqualError(err, `invalid authenticator type: "invalid", supported values are "webhook"`) + r.EqualError(err, `invalid authenticator type: "invalid", supported values are "webhook" and "jwt"`) }) }) @@ -292,5 +293,50 @@ func TestExchangeCredential(t *testing.T) { r.JSONEq(expected, buffer.String()) }) }) + + when("the authenticator info is passed", func() { + var actualAuthenticator corev1.TypedLocalObjectReference + + it.Before(func() { + tokenExchanger = func(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) { + actualAuthenticator = authenticator + return nil, nil + } + }) + + when("the authenticator is of type webhook", func() { + it.Before(func() { + fakeEnv["PINNIPED_AUTHENTICATOR_TYPE"] = "webhook" + fakeEnv["PINNIPED_AUTHENTICATOR_NAME"] = "some-webhook-name" + }) + + it("passes the correct authenticator type to the token exchanger", func() { + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) + r.NoError(err) + require.Equal(t, corev1.TypedLocalObjectReference{ + APIGroup: &auth1alpha1.SchemeGroupVersion.Group, + Kind: "WebhookAuthenticator", + Name: "some-webhook-name", + }, actualAuthenticator) + }) + }) + + when("the authenticator is of type jwt", func() { + it.Before(func() { + fakeEnv["PINNIPED_AUTHENTICATOR_TYPE"] = "jwt" + fakeEnv["PINNIPED_AUTHENTICATOR_NAME"] = "some-jwt-authenticator-name" + }) + + it("passes the correct authenticator type to the token exchanger", func() { + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) + r.NoError(err) + require.Equal(t, corev1.TypedLocalObjectReference{ + APIGroup: &auth1alpha1.SchemeGroupVersion.Group, + Kind: "JWTAuthenticator", + Name: "some-jwt-authenticator-name", + }, actualAuthenticator) + }) + }) + }) }, spec.Parallel(), spec.Report(report.Terminal{})) } diff --git a/cmd/pinniped/cmd/get_kubeconfig.go b/cmd/pinniped/cmd/get_kubeconfig.go index 3f4b22b8..9ed9bacf 100644 --- a/cmd/pinniped/cmd/get_kubeconfig.go +++ b/cmd/pinniped/cmd/get_kubeconfig.go @@ -89,7 +89,7 @@ func (c *getKubeConfigCommand) Command() *cobra.Command { cmd.Flags().StringVar(&c.flags.kubeconfig, "kubeconfig", c.flags.kubeconfig, "Path to the kubeconfig file") cmd.Flags().StringVar(&c.flags.contextOverride, "kubeconfig-context", c.flags.contextOverride, "Kubeconfig context override") cmd.Flags().StringVar(&c.flags.namespace, "pinniped-namespace", c.flags.namespace, "Namespace in which Pinniped was installed") - cmd.Flags().StringVar(&c.flags.authenticatorType, "authenticator-type", c.flags.authenticatorType, "Authenticator type (e.g., 'webhook')") + cmd.Flags().StringVar(&c.flags.authenticatorType, "authenticator-type", c.flags.authenticatorType, "Authenticator type (e.g., 'webhook', 'jwt')") cmd.Flags().StringVar(&c.flags.authenticatorName, "authenticator-name", c.flags.authenticatorType, "Authenticator name") mustMarkRequired(cmd, "token") plog.RemoveKlogGlobalFlags() diff --git a/cmd/pinniped/cmd/get_kubeconfig_test.go b/cmd/pinniped/cmd/get_kubeconfig_test.go index 70488b3c..40a88b90 100644 --- a/cmd/pinniped/cmd/get_kubeconfig_test.go +++ b/cmd/pinniped/cmd/get_kubeconfig_test.go @@ -31,7 +31,7 @@ var ( Flags: --authenticator-name string Authenticator name - --authenticator-type string Authenticator type (e.g., 'webhook') + --authenticator-type string Authenticator type (e.g., 'webhook', 'jwt') -h, --help help for get-kubeconfig --kubeconfig string Path to the kubeconfig file --kubeconfig-context string Kubeconfig context override @@ -62,7 +62,7 @@ var ( Flags: --authenticator-name string Authenticator name - --authenticator-type string Authenticator type (e.g., 'webhook') + --authenticator-type string Authenticator type (e.g., 'webhook', 'jwt') -h, --help help for get-kubeconfig --kubeconfig string Path to the kubeconfig file --kubeconfig-context string Kubeconfig context override From e0ee18a993c955b068c70216d285ee526dd91319 Mon Sep 17 00:00:00 2001 From: Andrew Keesler Date: Tue, 8 Dec 2020 11:08:53 -0500 Subject: [PATCH 4/6] Always close JWTAuthenticator underlying authenticator Otherwise we will leak goroutines. Signed-off-by: Andrew Keesler --- .../controller/authenticator/authenticator.go | 8 +++ .../cachecleaner/cachecleaner.go | 11 +-- .../cachecleaner/cachecleaner_test.go | 51 ++++++-------- .../jwtcachefiller/jwtcachefiller.go | 22 ++++-- .../jwtcachefiller/jwtcachefiller_test.go | 49 +++++++++++++- .../mocktokenauthenticatorcloser/generate.go | 21 ++++++ .../mocktokenauthenticatorcloser.go | 67 +++++++++++++++++++ 7 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 internal/mocks/mocktokenauthenticatorcloser/generate.go create mode 100644 internal/mocks/mocktokenauthenticatorcloser/mocktokenauthenticatorcloser.go diff --git a/internal/controller/authenticator/authenticator.go b/internal/controller/authenticator/authenticator.go index 16075ab5..0e412fc1 100644 --- a/internal/controller/authenticator/authenticator.go +++ b/internal/controller/authenticator/authenticator.go @@ -10,6 +10,14 @@ import ( auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" ) +// Closer is a type that can be closed impotently. +// +// This type is slightly different from io.Closer, because io.Closer can return an error and is not +// necessarily idempotent. +type Closer interface { + Close() +} + // CABundle returns a PEM-encoded CA bundle from the provided spec. If the provided spec is nil, a // nil CA bundle will be returned. If the provided spec contains a CA bundle that is not properly // encoded, an error will be returned. diff --git a/internal/controller/authenticator/cachecleaner/cachecleaner.go b/internal/controller/authenticator/cachecleaner/cachecleaner.go index ab762536..f400117a 100644 --- a/internal/controller/authenticator/cachecleaner/cachecleaner.go +++ b/internal/controller/authenticator/cachecleaner/cachecleaner.go @@ -14,16 +14,11 @@ import ( auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" authinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1" pinnipedcontroller "go.pinniped.dev/internal/controller" + "go.pinniped.dev/internal/controller/authenticator" "go.pinniped.dev/internal/controller/authenticator/authncache" "go.pinniped.dev/internal/controllerlib" ) -// closable is used to detect when a cache value has a Close() method on it. We use this to -// determine if we should call the Close() method on a cache value upon deleting it from the cache. -type closable interface { - Close() -} - // New instantiates a new controllerlib.Controller which will garbage collect authenticators from the provided Cache. func New( cache *authncache.Cache, @@ -108,8 +103,8 @@ func (c *controller) Sync(_ controllerlib.Context) error { ).Info("deleting authenticator from cache") value := c.cache.Get(key) - if closable, ok := value.(closable); ok { - closable.Close() + if closer, ok := value.(authenticator.Closer); ok { + closer.Close() } c.cache.Delete(key) diff --git a/internal/controller/authenticator/cachecleaner/cachecleaner_test.go b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go index 9e5cd147..db1038a7 100644 --- a/internal/controller/authenticator/cachecleaner/cachecleaner_test.go +++ b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -17,6 +18,7 @@ import ( pinnipedinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions" "go.pinniped.dev/internal/controller/authenticator/authncache" "go.pinniped.dev/internal/controllerlib" + "go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser" "go.pinniped.dev/internal/testutil/testlogger" ) @@ -57,16 +59,16 @@ func TestController(t *testing.T) { tests := []struct { name string objects []runtime.Object - initialCache map[authncache.Key]authncache.Value + initialCache func(t *testing.T, cache *authncache.Cache) wantErr string wantLogs []string wantCacheKeys []authncache.Key }{ { name: "no change", - initialCache: map[authncache.Key]authncache.Value{ - testWebhookKey1: nil, - testJWTAuthenticatorKey1: nil, + initialCache: func(t *testing.T, cache *authncache.Cache) { + cache.Store(testWebhookKey1, nil) + cache.Store(testJWTAuthenticatorKey1, nil) }, objects: []runtime.Object{ &authv1alpha.WebhookAuthenticator{ @@ -85,8 +87,7 @@ func TestController(t *testing.T) { wantCacheKeys: []authncache.Key{testWebhookKey1, testJWTAuthenticatorKey1}, }, { - name: "authenticators not yet added", - initialCache: nil, + name: "authenticators not yet added", objects: []runtime.Object{ &authv1alpha.WebhookAuthenticator{ ObjectMeta: metav1.ObjectMeta{ @@ -117,12 +118,12 @@ func TestController(t *testing.T) { }, { name: "successful cleanup", - initialCache: map[authncache.Key]authncache.Value{ - testWebhookKey1: nil, - testWebhookKey2: nil, - testJWTAuthenticatorKey1: newClosableCacheValue(t, "closable1", 0), - testJWTAuthenticatorKey2: newClosableCacheValue(t, "closable2", 1), - testKeyUnknownType: nil, + initialCache: func(t *testing.T, cache *authncache.Cache) { + cache.Store(testWebhookKey1, nil) + cache.Store(testWebhookKey2, nil) + cache.Store(testJWTAuthenticatorKey1, newClosableCacheValue(t, 0)) + cache.Store(testJWTAuthenticatorKey2, newClosableCacheValue(t, 1)) + cache.Store(testKeyUnknownType, nil) }, objects: []runtime.Object{ &authv1alpha.WebhookAuthenticator{ @@ -153,8 +154,8 @@ func TestController(t *testing.T) { fakeClient := pinnipedfake.NewSimpleClientset(tt.objects...) informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0) cache := authncache.New() - for k, v := range tt.initialCache { - cache.Store(k, v) + if tt.initialCache != nil { + tt.initialCache(t, cache) } testLog := testlogger.New(t) @@ -188,20 +189,10 @@ func TestController(t *testing.T) { } } -func newClosableCacheValue(t *testing.T, name string, wantCloses int) authncache.Value { - t.Helper() - c := &closableCacheValue{} - t.Cleanup(func() { - require.Equalf(t, wantCloses, c.closeCallCount, "expected %s.Close() to be called %d times", name, wantCloses) - }) - return c -} - -type closableCacheValue struct { - authncache.Value - closeCallCount int -} - -func (c *closableCacheValue) Close() { - c.closeCallCount++ +func newClosableCacheValue(t *testing.T, wantCloses int) authncache.Value { + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + tac := mocktokenauthenticatorcloser.NewMockTokenAuthenticatorCloser(ctrl) + tac.EXPECT().Close().Times(wantCloses) + return tac } diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go index 3028fbf8..ff53b9c7 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -85,17 +85,27 @@ func (c *controller) Sync(ctx controllerlib.Context) error { return fmt.Errorf("failed to get JWTAuthenticator %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err) } + cacheKey := authncache.Key{ + APIGroup: auth1alpha1.GroupName, + Kind: "JWTAuthenticator", + Namespace: ctx.Key.Namespace, + Name: ctx.Key.Name, + } + + // If this authenticator already exists, then we gotta make sure we close the old authenticator so + // we don't leak goroutines. + if value := c.cache.Get(cacheKey); value != nil { + if closer, ok := value.(authenticator.Closer); ok { + closer.Close() + } + } + jwtAuthenticator, err := newJWTAuthenticator(&obj.Spec) if err != nil { return fmt.Errorf("failed to build jwt authenticator: %w", err) } - c.cache.Store(authncache.Key{ - APIGroup: auth1alpha1.GroupName, - Kind: "JWTAuthenticator", - Namespace: ctx.Key.Namespace, - Name: ctx.Key.Name, - }, jwtAuthenticator) + c.cache.Store(cacheKey, jwtAuthenticator) c.log.WithValues("jwtAuthenticator", klog.KObj(obj), "issuer", obj.Spec.Issuer).Info("added new jwt authenticator") return nil } diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go index c5d453f2..10823c02 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -21,6 +21,7 @@ import ( "gopkg.in/square/go-jose.v2/jwt" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "gopkg.in/square/go-jose.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,6 +34,7 @@ import ( pinnipedinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions" "go.pinniped.dev/internal/controller/authenticator/authncache" "go.pinniped.dev/internal/controllerlib" + "go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser" "go.pinniped.dev/internal/testutil/testlogger" ) @@ -41,6 +43,7 @@ func TestController(t *testing.T) { tests := []struct { name string + cache func(*authncache.Cache) syncKey controllerlib.Key jwtAuthenticators []runtime.Object wantErr string @@ -75,6 +78,38 @@ func TestController(t *testing.T) { }, wantCacheEntries: 1, }, + { + name: "updating jwt authenticator closes previous instance", + cache: func(cache *authncache.Cache) { + cache.Store( + authncache.Key{ + Name: "test-name", + Namespace: "test-namespace", + Kind: "JWTAuthenticator", + APIGroup: auth1alpha1.SchemeGroupVersion.Group, + }, + newClosableCacheValue(t, 1), + ) + }, + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, + }, + }, + }, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, + }, + wantCacheEntries: 1, + }, { name: "valid jwt authenticator without CA", syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, @@ -124,6 +159,10 @@ func TestController(t *testing.T) { cache := authncache.New() testLog := testlogger.New(t) + if tt.cache != nil { + tt.cache(cache) + } + controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -432,7 +471,13 @@ func createJWT( jwt, err := builder.CompactSerialize() require.NoError(t, err) - t.Log("andrew:", jwt) - return jwt } + +func newClosableCacheValue(t *testing.T, wantCloses int) authncache.Value { + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + tac := mocktokenauthenticatorcloser.NewMockTokenAuthenticatorCloser(ctrl) + tac.EXPECT().Close().Times(wantCloses) + return tac +} diff --git a/internal/mocks/mocktokenauthenticatorcloser/generate.go b/internal/mocks/mocktokenauthenticatorcloser/generate.go new file mode 100644 index 00000000..878ae513 --- /dev/null +++ b/internal/mocks/mocktokenauthenticatorcloser/generate.go @@ -0,0 +1,21 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package mocktokenauthenticatorcloser + +import ( + "k8s.io/apiserver/pkg/authentication/authenticator" + + pinnipedauthenticator "go.pinniped.dev/internal/controller/authenticator" +) + +//go:generate go run -v github.com/golang/mock/mockgen -destination=mocktokenauthenticatorcloser.go -package=mocktokenauthenticatorcloser -copyright_file=../../../hack/header.txt . TokenAuthenticatorCloser + +// TokenAuthenticatorCloser is a type that can authenticate tokens and be closed idempotently. +// +// This type is slightly different from io.Closer, because io.Closer can return an error and is not +// necessarily idempotent. +type TokenAuthenticatorCloser interface { + authenticator.Token + pinnipedauthenticator.Closer +} diff --git a/internal/mocks/mocktokenauthenticatorcloser/mocktokenauthenticatorcloser.go b/internal/mocks/mocktokenauthenticatorcloser/mocktokenauthenticatorcloser.go new file mode 100644 index 00000000..c4ddc1f4 --- /dev/null +++ b/internal/mocks/mocktokenauthenticatorcloser/mocktokenauthenticatorcloser.go @@ -0,0 +1,67 @@ +// Copyright 2020 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser (interfaces: TokenAuthenticatorCloser) + +// Package mocktokenauthenticatorcloser is a generated GoMock package. +package mocktokenauthenticatorcloser + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + authenticator "k8s.io/apiserver/pkg/authentication/authenticator" + reflect "reflect" +) + +// MockTokenAuthenticatorCloser is a mock of TokenAuthenticatorCloser interface +type MockTokenAuthenticatorCloser struct { + ctrl *gomock.Controller + recorder *MockTokenAuthenticatorCloserMockRecorder +} + +// MockTokenAuthenticatorCloserMockRecorder is the mock recorder for MockTokenAuthenticatorCloser +type MockTokenAuthenticatorCloserMockRecorder struct { + mock *MockTokenAuthenticatorCloser +} + +// NewMockTokenAuthenticatorCloser creates a new mock instance +func NewMockTokenAuthenticatorCloser(ctrl *gomock.Controller) *MockTokenAuthenticatorCloser { + mock := &MockTokenAuthenticatorCloser{ctrl: ctrl} + mock.recorder = &MockTokenAuthenticatorCloserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockTokenAuthenticatorCloser) EXPECT() *MockTokenAuthenticatorCloserMockRecorder { + return m.recorder +} + +// AuthenticateToken mocks base method +func (m *MockTokenAuthenticatorCloser) AuthenticateToken(arg0 context.Context, arg1 string) (*authenticator.Response, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthenticateToken", arg0, arg1) + ret0, _ := ret[0].(*authenticator.Response) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// AuthenticateToken indicates an expected call of AuthenticateToken +func (mr *MockTokenAuthenticatorCloserMockRecorder) AuthenticateToken(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateToken", reflect.TypeOf((*MockTokenAuthenticatorCloser)(nil).AuthenticateToken), arg0, arg1) +} + +// Close mocks base method +func (m *MockTokenAuthenticatorCloser) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockTokenAuthenticatorCloserMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTokenAuthenticatorCloser)(nil).Close)) +} From 9ed5dcb0316c74d06e4f8509ebcaf159a64a6885 Mon Sep 17 00:00:00 2001 From: Aram Price Date: Tue, 8 Dec 2020 15:14:05 -0500 Subject: [PATCH 5/6] Only create underlying jwt authenticator when spec has changed Signed-off-by: Andrew Keesler --- .../jwtcachefiller/jwtcachefiller.go | 61 ++++++-- .../jwtcachefiller/jwtcachefiller_test.go | 132 ++++++++++++++---- 2 files changed, 155 insertions(+), 38 deletions(-) diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go index ff53b9c7..de31961a 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -9,17 +9,19 @@ import ( "fmt" "io/ioutil" "os" + "reflect" "github.com/go-logr/logr" "gopkg.in/square/go-jose.v2" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" "k8s.io/klog/v2" auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" authinformers "go.pinniped.dev/generated/1.19/client/concierge/informers/externalversions/authentication/v1alpha1" pinnipedcontroller "go.pinniped.dev/internal/controller" - "go.pinniped.dev/internal/controller/authenticator" + pinnipedauthenticator "go.pinniped.dev/internal/controller/authenticator" "go.pinniped.dev/internal/controller/authenticator/authncache" "go.pinniped.dev/internal/controllerlib" ) @@ -45,6 +47,16 @@ func defaultSupportedSigningAlgos() []string { } } +type tokenAuthenticatorCloser interface { + authenticator.Token + pinnipedauthenticator.Closer +} + +type jwtAuthenticator struct { + tokenAuthenticatorCloser + spec *auth1alpha1.JWTAuthenticatorSpec +} + // New instantiates a new controllerlib.Controller which will populate the provided authncache.Cache. func New( cache *authncache.Cache, @@ -92,15 +104,25 @@ func (c *controller) Sync(ctx controllerlib.Context) error { Name: ctx.Key.Name, } - // If this authenticator already exists, then we gotta make sure we close the old authenticator so - // we don't leak goroutines. + // If this authenticator already exists, then only recreate it if is different from the desired + // authenticator. We don't want to be creating a new authenticator for every resync period. + // + // If we do need to recreate the authenticator, then make sure we close the old one to avoid + // goroutine leaks. if value := c.cache.Get(cacheKey); value != nil { - if closer, ok := value.(authenticator.Closer); ok { - closer.Close() + jwtAuthenticator := c.extractValueAsJWTAuthenticator(value) + if jwtAuthenticator != nil { + if reflect.DeepEqual(jwtAuthenticator.spec, &obj.Spec) { + c.log.WithValues("jwtAuthenticator", klog.KObj(obj), "issuer", obj.Spec.Issuer).Info("actual jwt authenticator and desired jwt authenticator are the same") + return nil + } + jwtAuthenticator.Close() } } - jwtAuthenticator, err := newJWTAuthenticator(&obj.Spec) + // 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) } @@ -110,9 +132,22 @@ func (c *controller) Sync(ctx controllerlib.Context) error { return nil } +func (c *controller) extractValueAsJWTAuthenticator(value authncache.Value) *jwtAuthenticator { + jwtAuthenticator, ok := value.(*jwtAuthenticator) + if !ok { + actualType := "" + if t := reflect.TypeOf(value); t != nil { + actualType = t.String() + } + c.log.WithValues("actualType", actualType).Info("wrong JWT authenticator type in cache") + return nil + } + return jwtAuthenticator +} + // newJWTAuthenticator creates a jwt authenticator from the provided spec. -func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*oidc.Authenticator, error) { - caBundle, err := authenticator.CABundle(spec.TLS) +func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthenticator, error) { + caBundle, err := pinnipedauthenticator.CABundle(spec.TLS) if err != nil { return nil, fmt.Errorf("invalid TLS configuration: %w", err) } @@ -135,7 +170,7 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*oidc.Authenti caFile = temp.Name() } - return oidc.New(oidc.Options{ + authenticator, err := oidc.New(oidc.Options{ IssuerURL: spec.Issuer, ClientID: spec.Audience, UsernameClaim: defaultUsernameClaim, @@ -143,4 +178,12 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*oidc.Authenti SupportedSigningAlgs: defaultSupportedSigningAlgos(), CAFile: caFile, }) + if err != nil { + return nil, fmt.Errorf("could not initialize authenticator: %w", err) + } + + return &jwtAuthenticator{ + tokenAuthenticatorCloser: authenticator, + spec: spec, + }, nil } diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go index 10823c02..eaafa798 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller_test.go @@ -41,9 +41,30 @@ import ( func TestController(t *testing.T) { t.Parallel() + someJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, + } + otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-other-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, + } + missingTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-issuer.com", + Audience: "some-audience", + } + invalidTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{ + Issuer: "https://some-other-issuer.com", + Audience: "some-audience", + TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid base64-encoded data"}, + } + tests := []struct { name string - cache func(*authncache.Cache) + cache func(*testing.T, *authncache.Cache, bool) + wantClose bool syncKey controllerlib.Key jwtAuthenticators []runtime.Object wantErr string @@ -66,11 +87,7 @@ func TestController(t *testing.T) { Namespace: "test-namespace", Name: "test-name", }, - Spec: auth1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://some-issuer.com", - Audience: "some-audience", - TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, - }, + Spec: *someJWTAuthenticatorSpec, }, }, wantLogs: []string{ @@ -79,8 +96,8 @@ func TestController(t *testing.T) { wantCacheEntries: 1, }, { - name: "updating jwt authenticator closes previous instance", - cache: func(cache *authncache.Cache) { + name: "updating jwt authenticator with new fields closes previous instance", + cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { cache.Store( authncache.Key{ Name: "test-name", @@ -88,7 +105,65 @@ func TestController(t *testing.T) { Kind: "JWTAuthenticator", APIGroup: auth1alpha1.SchemeGroupVersion.Group, }, - newClosableCacheValue(t, 1), + newCacheValue(t, *otherJWTAuthenticatorSpec, wantClose), + ) + }, + wantClose: true, + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + }, + }, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, + }, + wantCacheEntries: 1, + }, + { + name: "updating jwt authenticator with the same value does nothing", + cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { + cache.Store( + authncache.Key{ + Name: "test-name", + Namespace: "test-namespace", + Kind: "JWTAuthenticator", + APIGroup: auth1alpha1.SchemeGroupVersion.Group, + }, + newCacheValue(t, *someJWTAuthenticatorSpec, wantClose), + ) + }, + wantClose: false, + syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, + jwtAuthenticators: []runtime.Object{ + &auth1alpha1.JWTAuthenticator{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-name", + }, + Spec: *someJWTAuthenticatorSpec, + }, + }, + wantLogs: []string{ + `jwtcachefiller-controller "level"=0 "msg"="actual jwt authenticator and desired jwt authenticator are the same" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, + }, + wantCacheEntries: 1, + }, + { + name: "updating jwt authenticator when cache value is wrong type", + cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) { + cache.Store( + authncache.Key{ + Name: "test-name", + Namespace: "test-namespace", + Kind: "JWTAuthenticator", + APIGroup: auth1alpha1.SchemeGroupVersion.Group, + }, + struct{ authenticator.Token }{}, ) }, syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"}, @@ -98,14 +173,11 @@ func TestController(t *testing.T) { Namespace: "test-namespace", Name: "test-name", }, - Spec: auth1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://some-issuer.com", - Audience: "some-audience", - TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}, - }, + 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"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`, }, wantCacheEntries: 1, @@ -119,10 +191,7 @@ func TestController(t *testing.T) { Namespace: "test-namespace", Name: "test-name", }, - Spec: auth1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://some-issuer.com", - Audience: "some-audience", - }, + Spec: *missingTLSJWTAuthenticatorSpec, }, }, wantLogs: []string{ @@ -139,14 +208,10 @@ func TestController(t *testing.T) { Namespace: "test-namespace", Name: "test-name", }, - Spec: auth1alpha1.JWTAuthenticatorSpec{ - Issuer: "https://some-issuer.com", - Audience: "some-audience", - TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "not base64-encoded"}, - }, + Spec: *invalidTLSJWTAuthenticatorSpec, }, }, - wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 3", + wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7", }, } for _, tt := range tests { @@ -160,7 +225,7 @@ func TestController(t *testing.T) { testLog := testlogger.New(t) if tt.cache != nil { - tt.cache(cache) + tt.cache(t, cache, tt.wantClose) } controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog) @@ -474,10 +539,19 @@ func createJWT( return jwt } -func newClosableCacheValue(t *testing.T, wantCloses int) authncache.Value { +func newCacheValue(t *testing.T, spec auth1alpha1.JWTAuthenticatorSpec, wantClose bool) authncache.Value { ctrl := gomock.NewController(t) t.Cleanup(ctrl.Finish) - tac := mocktokenauthenticatorcloser.NewMockTokenAuthenticatorCloser(ctrl) - tac.EXPECT().Close().Times(wantCloses) - return tac + tokenAuthenticatorCloser := mocktokenauthenticatorcloser.NewMockTokenAuthenticatorCloser(ctrl) + + wantCloses := 0 + if wantClose { + wantCloses++ + } + tokenAuthenticatorCloser.EXPECT().Close().Times(wantCloses) + + return &jwtAuthenticator{ + tokenAuthenticatorCloser: tokenAuthenticatorCloser, + spec: &spec, + } } From 381a2e749a1710158a3afbc3f0b4f7e889dd70d9 Mon Sep 17 00:00:00 2001 From: Andrew Keesler Date: Tue, 8 Dec 2020 15:36:27 -0500 Subject: [PATCH 6/6] impotent -> idempotent These words do not mean the same thing... Signed-off-by: Andrew Keesler --- internal/controller/authenticator/authenticator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/authenticator/authenticator.go b/internal/controller/authenticator/authenticator.go index 0e412fc1..bad487e1 100644 --- a/internal/controller/authenticator/authenticator.go +++ b/internal/controller/authenticator/authenticator.go @@ -10,7 +10,7 @@ import ( auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1" ) -// Closer is a type that can be closed impotently. +// Closer is a type that can be closed idempotently. // // This type is slightly different from io.Closer, because io.Closer can return an error and is not // necessarily idempotent.