From 59d67322d3a91a5c222a943cb47668b529f9f462 Mon Sep 17 00:00:00 2001 From: Monis Khan Date: Mon, 13 Jun 2022 20:06:47 -0400 Subject: [PATCH] Static validation for OIDC clients The following validation is enforced: 1. Names must start with client.oauth.pinniped.dev- 2. Redirect URIs must start with https:// or http://127.0.0.1 or http://::1 3. All spec lists must not have duplicates Added an integration test to assert all static validations. Signed-off-by: Monis Khan --- .../config/v1alpha1/types_oidcclient.go.tmpl | 11 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- deploy/supervisor/z0_crd_overlay.yaml | 12 + generated/1.17/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.18/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.19/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.20/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.21/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.22/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.23/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- generated/1.24/README.adoc | 2 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- ...g.supervisor.pinniped.dev_oidcclients.yaml | 10 +- .../config/v1alpha1/types_oidcclient.go | 11 +- .../config/v1alpha1/zz_generated.deepcopy.go | 2 +- internal/oidc/oidc.go | 3 + test/integration/oidc_client_test.go | 408 ++++++++++++++++++ 39 files changed, 602 insertions(+), 55 deletions(-) create mode 100644 test/integration/oidc_client_test.go diff --git a/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl b/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl index e905c61a..17a1103f 100644 --- a/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl +++ b/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml b/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/deploy/supervisor/z0_crd_overlay.yaml b/deploy/supervisor/z0_crd_overlay.yaml index a658091b..f7a50a88 100644 --- a/deploy/supervisor/z0_crd_overlay.yaml +++ b/deploy/supervisor/z0_crd_overlay.yaml @@ -49,3 +49,15 @@ metadata: name: #@ pinnipedDevAPIGroupWithPrefix("oidcclients.config.supervisor") spec: group: #@ pinnipedDevAPIGroupWithPrefix("config.supervisor") + versions: + #@overlay/match by=overlay.all, expects="1+" + - schema: + openAPIV3Schema: + #@overlay/match by=overlay.subset({"metadata":{"type":"object"}}), expects=1 + properties: + metadata: + #@overlay/match missing_ok=True + properties: + name: + pattern: ^client\.oauth\.pinniped\.dev- + type: string diff --git a/generated/1.17/README.adoc b/generated/1.17/README.adoc index 624f035f..33ccf479 100644 --- a/generated/1.17/README.adoc +++ b/generated/1.17/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.17/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.17/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.17/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.17/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.17/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.17/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.17/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.17/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.17/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.18/README.adoc b/generated/1.18/README.adoc index 63ec9f13..c8f1cdb1 100644 --- a/generated/1.18/README.adoc +++ b/generated/1.18/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.18/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.18/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.18/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.18/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.18/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.18/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.18/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.18/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.18/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.19/README.adoc b/generated/1.19/README.adoc index f04d438f..2db7eb41 100644 --- a/generated/1.19/README.adoc +++ b/generated/1.19/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.19/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.19/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.19/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.19/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.19/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.19/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.19/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.19/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.19/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.20/README.adoc b/generated/1.20/README.adoc index 2e989cd3..2eaf98f6 100644 --- a/generated/1.20/README.adoc +++ b/generated/1.20/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.20/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.20/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.20/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.20/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.20/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.20/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.20/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.20/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.20/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.21/README.adoc b/generated/1.21/README.adoc index 7635b9a6..5a8ed2ea 100644 --- a/generated/1.21/README.adoc +++ b/generated/1.21/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.21/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.22/README.adoc b/generated/1.22/README.adoc index 5ba5e839..39973f52 100644 --- a/generated/1.22/README.adoc +++ b/generated/1.22/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.22/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.23/README.adoc b/generated/1.23/README.adoc index 78612146..85ea04f0 100644 --- a/generated/1.23/README.adoc +++ b/generated/1.23/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.23/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.24/README.adoc b/generated/1.24/README.adoc index 9255c3d4..1280132f 100644 --- a/generated/1.24/README.adoc +++ b/generated/1.24/README.adoc @@ -578,7 +578,7 @@ OIDCClientSpec is a struct that describes an OIDC Client. [cols="25a,75a", options="header"] |=== | Field | Description -| *`allowedRedirectURIs`* __string array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be https, unless it is a loopback. +| *`allowedRedirectURIs`* __RedirectURI array__ | allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this client. Any other uris will be rejected. Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. | *`allowedGrantTypes`* __GrantType array__ | allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this client. Must only contain the following values: - authorization_code: allows the client to perform the authorization code grant flow, i.e. allows the webapp to authenticate users. This grant must always be listed. - refresh_token: allows the client to perform refresh grants for the user to extend the user's session. This grant must be listed if allowedScopes lists offline_access. - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. This grant must be listed if allowedScopes lists pinniped:request-audience. | *`allowedScopes`* __Scope array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. diff --git a/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.24/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 4efa445e..6030582f 100644 --- a/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -61,15 +61,20 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedRedirectURIs: description: allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this - client. Any other uris will be rejected. Must be https, unless it - is a loopback. + client. Any other uris will be rejected. Must be a URI with the + https scheme, unless the hostname is 127.0.0.1 or ::1 which may + use the http scheme. Port numbers are not required for 127.0.0.1 + or ::1 and are ignored when checking for a matching redirect_uri. items: + pattern: ^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/ type: string minItems: 1 type: array + x-kubernetes-list-type: set allowedScopes: description: "allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. @@ -102,6 +107,7 @@ spec: type: string minItems: 1 type: array + x-kubernetes-list-type: set required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go index e905c61a..17a1103f 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -7,6 +7,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` +type RedirectURI string + // +kubebuilder:validation:Enum="authorization_code";"refresh_token";"urn:ietf:params:oauth:grant-type:token-exchange" type GrantType string @@ -17,9 +20,11 @@ type Scope string type OIDCClientSpec struct { // allowedRedirectURIs is a list of the allowed redirect_uri param values that should be accepted during OIDC flows with this // client. Any other uris will be rejected. - // Must be https, unless it is a loopback. + // Must be a URI with the https scheme, unless the hostname is 127.0.0.1 or ::1 which may use the http scheme. + // Port numbers are not required for 127.0.0.1 or ::1 and are ignored when checking for a matching redirect_uri. + // +listType=set // +kubebuilder:validation:MinItems=1 - AllowedRedirectURIs []string `json:"allowedRedirectURIs"` + AllowedRedirectURIs []RedirectURI `json:"allowedRedirectURIs"` // allowedGrantTypes is a list of the allowed grant_type param values that should be accepted during OIDC flows with this // client. @@ -32,6 +37,7 @@ type OIDCClientSpec struct { // - urn:ietf:params:oauth:grant-type:token-exchange: allows the client to perform RFC8693 token exchange, // which is a step in the process to be able to get a cluster credential for the user. // This grant must be listed if allowedScopes lists pinniped:request-audience. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedGrantTypes []GrantType `json:"allowedGrantTypes"` @@ -51,6 +57,7 @@ type OIDCClientSpec struct { // - groups: The client is allowed to request that ID tokens contain the user's group membership, // if their group membership is discoverable by the Supervisor. // Without the groups scope being requested and allowed, the ID token will not contain groups. + // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` } diff --git a/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index a55d88e7..f4468886 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -217,7 +217,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = *in if in.AllowedRedirectURIs != nil { in, out := &in.AllowedRedirectURIs, &out.AllowedRedirectURIs - *out = make([]string, len(*in)) + *out = make([]RedirectURI, len(*in)) copy(*out, *in) } if in.AllowedGrantTypes != nil { diff --git a/internal/oidc/oidc.go b/internal/oidc/oidc.go index 79380df7..1c5b7237 100644 --- a/internal/oidc/oidc.go +++ b/internal/oidc/oidc.go @@ -229,6 +229,9 @@ func FositeOauth2Helper( // Use the fosite default to make it more likely that off the shelf OIDC clients can work with the supervisor. MinParameterEntropy: fosite.MinParameterEntropy, + + // do not allow custom scheme redirects, only https and http (on loopback) + RedirectSecureChecker: fosite.IsRedirectURISecureStrict, } provider := compose.Compose( diff --git a/test/integration/oidc_client_test.go b/test/integration/oidc_client_test.go new file mode 100644 index 00000000..be987db9 --- /dev/null +++ b/test/integration/oidc_client_test.go @@ -0,0 +1,408 @@ +// Copyright 2022 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package integration + +import ( + "context" + "fmt" + "sort" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + supervisorconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1" + "go.pinniped.dev/test/testlib" +) + +func TestOIDCClientStaticValidation_Parallel(t *testing.T) { + env := testlib.IntegrationEnv(t) + + groupFix := strings.NewReplacer(".supervisor.pinniped.dev", ".supervisor."+env.APIGroupSuffix) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + t.Cleanup(cancel) + + namespaceClient := testlib.NewKubernetesClientset(t).CoreV1().Namespaces() + + ns, err := namespaceClient.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-oidc-client-", + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, namespaceClient.Delete(ctx, ns.Name, metav1.DeleteOptions{})) + }) + + oidcClients := testlib.NewSupervisorClientset(t).ConfigV1alpha1().OIDCClients(ns.Name) + + tests := []struct { + name string + client *supervisorconfigv1alpha1.OIDCClient + fixWant func(t *testing.T, err error, want string) string + wantErr string + }{ + { + name: "bad name", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "panda", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "https://a", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "panda" is invalid: metadata.name: Invalid value: "panda": metadata.name in body should match '^client\.oauth\.pinniped\.dev-'`, + }, + { + name: "bad name but close", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client0oauth1pinniped2dev-regex", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "https://a", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client0oauth1pinniped2dev-regex" is invalid: metadata.name: Invalid value: "client0oauth1pinniped2dev-regex": metadata.name in body should match '^client\.oauth\.pinniped\.dev-'`, + }, + { + name: "bad generate name", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "snorlax-", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + fixWant: func(t *testing.T, err error, want string) string { + require.Error(t, err) + gotErr := err.Error() + errPrefix := groupFix.Replace(`OIDCClient.config.supervisor.pinniped.dev "snorlax-`) + require.True(t, strings.HasPrefix(gotErr, errPrefix)) + gotErr = strings.TrimPrefix(gotErr, errPrefix) + end := strings.Index(gotErr, `"`) + require.Equal(t, end, 5) + gotErr = gotErr[:end] + return strings.Replace(want, "RAND", gotErr, 2) + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "snorlax-RAND" is invalid: metadata.name: Invalid value: "snorlax-RAND": metadata.name in body should match '^client\.oauth\.pinniped\.dev-'`, + }, + { + name: "bad redirect uri", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-hello", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + "oob", + "https://a", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-hello" is invalid: spec.allowedRedirectURIs[1]: Invalid value: "oob": spec.allowedRedirectURIs[1] in body should match '^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/'`, + }, + { + name: "bad grant type", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-sky", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + "authorization_code", + "bird", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-sky" is invalid: spec.allowedGrantTypes[2]: Unsupported value: "bird": supported values: "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"`, + }, + { + name: "bad scope", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-blue", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "*", + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-blue" is invalid: spec.allowedScopes[0]: Unsupported value: "*": supported values: "openid", "offline_access", "username", "groups", "pinniped:request-audience"`, + }, + { + name: "empty unset all", + client: &supervisorconfigv1alpha1.OIDCClient{}, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "" is invalid: [metadata.name: Required value: name or generateName is required, spec.allowedGrantTypes: Required value, spec.allowedRedirectURIs: Required value, spec.allowedScopes: Required value]`, + }, + { + name: "empty uris", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-green-1", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{}, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-green-1" is invalid: spec.allowedRedirectURIs: Invalid value: 0: spec.allowedRedirectURIs in body should have at least 1 items`, + }, + { + name: "empty grants", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-green-2", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{}, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-green-2" is invalid: spec.allowedGrantTypes: Invalid value: 0: spec.allowedGrantTypes in body should have at least 1 items`, + }, + { + name: "empty scopes", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-green-3", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{}, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-green-3" is invalid: spec.allowedScopes: Invalid value: 0: spec.allowedScopes in body should have at least 1 items`, + }, + { + name: "duplicate uris", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-red-1", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-red-1" is invalid: spec.allowedRedirectURIs[1]: Duplicate value: "http://127.0.0.1/callback"`, + }, + { + name: "duplicate grants", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-red-2", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-red-2" is invalid: spec.allowedGrantTypes[1]: Duplicate value: "refresh_token"`, + }, + { + name: "duplicate scopes", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-red-3", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + "username", + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-red-3" is invalid: spec.allowedScopes[1]: Duplicate value: "username"`, + }, + { + name: "bad everything", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "zone", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "of", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "the", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "enders", + }, + }, + }, + fixWant: func(t *testing.T, err error, want string) string { + // sort the error causes and use that to rebuild a sorted error message + statusErr := &errors.StatusError{} + require.ErrorAs(t, err, &statusErr) + require.Len(t, statusErr.ErrStatus.Details.Causes, 4) + out := make([]string, 0, len(statusErr.ErrStatus.Details.Causes)) + for _, cause := range statusErr.ErrStatus.Details.Causes { + cause := cause + out = append(out, fmt.Sprintf("%s: %s", cause.Field, cause.Message)) + } + sort.Strings(out) + errPrefix := groupFix.Replace(`OIDCClient.config.supervisor.pinniped.dev "zone" is invalid: [`) + require.True(t, strings.HasPrefix(err.Error(), errPrefix)) + require.Equal(t, err.Error(), statusErr.ErrStatus.Message) + statusErr.ErrStatus.Message = errPrefix + strings.Join(out, ", ") + "]" + return want // leave the wanted error unchanged + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "zone" is invalid: [metadata.name: Invalid value: "zone": metadata.name in body should match '^client\.oauth\.pinniped\.dev-', spec.allowedGrantTypes[0]: Unsupported value: "the": supported values: "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange", spec.allowedRedirectURIs[0]: Invalid value: "of": spec.allowedRedirectURIs[0] in body should match '^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/', spec.allowedScopes[0]: Unsupported value: "enders": supported values: "openid", "offline_access", "username", "groups", "pinniped:request-audience"]`, + }, + { + name: "everything valid", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-lava", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "https://example.com", + "http://127.0.0.1/yoyo", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "authorization_code", + "refresh_token", + "urn:ietf:params:oauth:grant-type:token-exchange", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "openid", + "offline_access", + "username", + "groups", + "pinniped:request-audience", + }, + }, + }, + wantErr: "", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + client, err := oidcClients.Create(ctx, tt.client, metav1.CreateOptions{}) + + want := tt.wantErr + + if len(want) == 0 { + require.NoError(t, err) + + // unset server generated fields + client.Namespace = "" + client.UID = "" + client.ResourceVersion = "" + client.ManagedFields = nil + client.CreationTimestamp = metav1.Time{} + client.Generation = 0 + + require.Equal(t, tt.client, client) + return + } + + if tt.fixWant != nil { + want = tt.fixWant(t, err, want) + } + + want = groupFix.Replace(want) + + require.EqualError(t, err, want) + }) + } +}