From def2b35e6e8ece979e34fb2c75d5f4fc8a6bdb53 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 2 Feb 2024 10:57:57 -0800 Subject: [PATCH] Make ID token lifetimes configurable on OIDCClient resources --- .../config/v1alpha1/types_oidcclient.go.tmpl | 24 +++- ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.21/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.22/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.23/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.24/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.25/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.26/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.27/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.28/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/1.29/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ ...g.supervisor.pinniped.dev_oidcclients.yaml | 21 +++ generated/latest/README.adoc | 18 +++ .../config/v1alpha1/types_oidcclient.go | 24 +++- .../config/v1alpha1/zz_generated.deepcopy.go | 22 +++ internal/crud/crud.go | 20 ++- .../clientregistry/clientregistry.go | 18 ++- .../endpoints/token/token_handler.go | 30 +++- .../endpointsmanager/manager.go | 4 +- .../idtokenlifespan/idtoken_lifespan.go | 55 +++++++ internal/federationdomain/oidc/oidc.go | 136 +++++++++++++++--- .../timeouts/timeouts_configuration.go | 40 +++++- .../fositestorage/accesstoken/accesstoken.go | 14 +- .../authorizationcode/authorizationcode.go | 18 ++- .../openidconnect/openidconnect.go | 18 ++- internal/fositestorage/pkce/pkce.go | 18 ++- .../refreshtoken/refreshtoken.go | 14 +- .../oidcclientsecretstorage.go | 6 +- .../supervisor_oidc_client_test.go | 51 ++++++- 55 files changed, 1238 insertions(+), 78 deletions(-) create mode 100644 internal/federationdomain/idtokenlifespan/idtoken_lifespan.go diff --git a/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl b/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl index 61106fdba..b02307f94 100644 --- a/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl +++ b/apis/supervisor/config/v1alpha1/types_oidcclient.go.tmpl @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml b/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/deploy/supervisor/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.21/README.adoc b/generated/1.21/README.adoc index 677bd9552..3e0a81969 100644 --- a/generated/1.21/README.adoc +++ b/generated/1.21/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) 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 61106fdba..b02307f94 100644 --- a/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.21/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. 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 ad308c3de..d3c7ec69e 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 @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} 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 30c154f7e..40cb1e21e 100644 --- a/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.21/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.22/README.adoc b/generated/1.22/README.adoc index de663d43f..412049ec8 100644 --- a/generated/1.22/README.adoc +++ b/generated/1.22/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) 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 61106fdba..b02307f94 100644 --- a/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.22/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. 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 ad308c3de..d3c7ec69e 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 @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} 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 30c154f7e..40cb1e21e 100644 --- a/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.22/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.23/README.adoc b/generated/1.23/README.adoc index d542fc70c..f4b6d03f2 100644 --- a/generated/1.23/README.adoc +++ b/generated/1.23/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) 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 61106fdba..b02307f94 100644 --- a/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.23/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. 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 ad308c3de..d3c7ec69e 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 @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} 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 0960ca0de..d33b31bf1 100644 --- a/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.23/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.24/README.adoc b/generated/1.24/README.adoc index 782f1cc2b..39889c3f0 100644 --- a/generated/1.24/README.adoc +++ b/generated/1.24/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) 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 61106fdba..b02307f94 100644 --- a/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.24/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. 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 ad308c3de..d3c7ec69e 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 @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} 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 0960ca0de..d33b31bf1 100644 --- a/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.24/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.25/README.adoc b/generated/1.25/README.adoc index cb137d202..08e3839ea 100644 --- a/generated/1.25/README.adoc +++ b/generated/1.25/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/1.25/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.25/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/1.25/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.25/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index ad308c3de..d3c7ec69e 100644 --- a/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.25/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/generated/1.25/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.25/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/generated/1.25/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.25/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.26/README.adoc b/generated/1.26/README.adoc index 17170f767..d776379c2 100644 --- a/generated/1.26/README.adoc +++ b/generated/1.26/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/1.26/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.26/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/1.26/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.26/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index ad308c3de..d3c7ec69e 100644 --- a/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.26/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/generated/1.26/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.26/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/generated/1.26/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.26/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.27/README.adoc b/generated/1.27/README.adoc index 026512700..c0276b3b1 100644 --- a/generated/1.27/README.adoc +++ b/generated/1.27/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/1.27/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.27/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/1.27/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.27/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/generated/1.27/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.27/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index ad308c3de..d3c7ec69e 100644 --- a/generated/1.27/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.27/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/generated/1.27/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.27/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/generated/1.27/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.27/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.28/README.adoc b/generated/1.28/README.adoc index 9f6f45886..36162c948 100644 --- a/generated/1.28/README.adoc +++ b/generated/1.28/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/1.28/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.28/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/1.28/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.28/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/generated/1.28/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.28/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index ad308c3de..d3c7ec69e 100644 --- a/generated/1.28/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.28/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/generated/1.28/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.28/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/generated/1.28/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.28/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/1.29/README.adoc b/generated/1.29/README.adoc index dbecc10cf..ea1a39312 100644 --- a/generated/1.29/README.adoc +++ b/generated/1.29/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/1.29/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/1.29/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/1.29/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/1.29/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. diff --git a/generated/1.29/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go b/generated/1.29/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go index ad308c3de..d3c7ec69e 100644 --- a/generated/1.29/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/1.29/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/generated/1.29/crds/config.supervisor.pinniped.dev_oidcclients.yaml b/generated/1.29/crds/config.supervisor.pinniped.dev_oidcclients.yaml index 0960ca0de..d33b31bf1 100644 --- a/generated/1.29/crds/config.supervisor.pinniped.dev_oidcclients.yaml +++ b/generated/1.29/crds/config.supervisor.pinniped.dev_oidcclients.yaml @@ -119,6 +119,27 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + tokenLifetimes: + description: tokenLifetimes are the optional overrides of token lifetimes + for an OIDCClient. + properties: + idTokenSeconds: + description: |- + idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + external identity provider to decide if it is acceptable for the end user to continue their session, and will + update the end user's group memberships from the external identity provider. Giving these tokens a long life is + will allow the end user to continue to use a token while avoiding these updates from the external identity + provider. However, some web applications may have reasons specific to the design of that application to prefer + longer lifetimes. + format: int32 + maximum: 1800 + minimum: 120 + type: integer + type: object required: - allowedGrantTypes - allowedRedirectURIs diff --git a/generated/latest/README.adoc b/generated/latest/README.adoc index dbecc10cf..ea1a39312 100644 --- a/generated/latest/README.adoc +++ b/generated/latest/README.adoc @@ -937,6 +937,7 @@ Must only contain the following values: - authorization_code: allows the client | *`allowedScopes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-scope[$$Scope$$] array__ | allowedScopes is a list of the allowed scopes param values that should be accepted during OIDC flows with this client. + Must only contain the following values: - openid: The client is allowed to request ID tokens. ID tokens only include the required claims by default (iss, sub, aud, exp, iat). This scope must always be listed. - offline_access: The client is allowed to request an initial refresh token during the authorization code grant flow. This scope must be listed if allowedGrantTypes lists refresh_token. - pinniped:request-audience: The client is allowed to request a new audience value during a RFC8693 token exchange, which is a step in the process to be able to get a cluster credential for the user. openid, username and groups scopes must be listed when this scope is present. This scope must be listed if allowedGrantTypes lists urn:ietf:params:oauth:grant-type:token-exchange. - username: The client is allowed to request that ID tokens contain the user's username. Without the username scope being requested and allowed, the ID token will not contain the user's username. - 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. +| *`tokenLifetimes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes[$$OIDCClientTokenLifetimes$$]__ | tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. |=== @@ -959,6 +960,23 @@ OIDCClientStatus is a struct that describes the actual state of an OIDCClient. |=== +[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclienttokenlifetimes"] +==== OIDCClientTokenLifetimes + +OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. + +.Appears In: +**** +- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-oidcclientspec[$$OIDCClientSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`idTokenSeconds`* __integer__ | idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the external identity provider to decide if it is acceptable for the end user to continue their session, and will update the end user's group memberships from the external identity provider. Giving these tokens a long life is will allow the end user to continue to use a token while avoiding these updates from the external identity provider. However, some web applications may have reasons specific to the design of that application to prefer longer lifetimes. +|=== + + [id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-supervisor-config-v1alpha1-redirecturi"] ==== RedirectURI (string) diff --git a/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go b/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go index 61106fdba..b02307f94 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/types_oidcclient.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package v1alpha1 @@ -71,6 +71,28 @@ type OIDCClientSpec struct { // +listType=set // +kubebuilder:validation:MinItems=1 AllowedScopes []Scope `json:"allowedScopes"` + + // tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient. + // +optional + TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"` +} + +// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient. +type OIDCClientTokenLifetimes struct { + // idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of + // ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID + // tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used. + // This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens + // short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the + // external identity provider to decide if it is acceptable for the end user to continue their session, and will + // update the end user's group memberships from the external identity provider. Giving these tokens a long life is + // will allow the end user to continue to use a token while avoiding these updates from the external identity + // provider. However, some web applications may have reasons specific to the design of that application to prefer + // longer lifetimes. + // +kubebuilder:validation:Minimum=120 + // +kubebuilder:validation:Maximum=1800 + // +optional + IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"` } // OIDCClientStatus is a struct that describes the actual state of an OIDCClient. 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 ad308c3de..d3c7ec69e 100644 --- a/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go +++ b/generated/latest/apis/supervisor/config/v1alpha1/zz_generated.deepcopy.go @@ -374,6 +374,7 @@ func (in *OIDCClientSpec) DeepCopyInto(out *OIDCClientSpec) { *out = make([]Scope, len(*in)) copy(*out, *in) } + in.TokenLifetimes.DeepCopyInto(&out.TokenLifetimes) return } @@ -409,3 +410,24 @@ func (in *OIDCClientStatus) DeepCopy() *OIDCClientStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OIDCClientTokenLifetimes) DeepCopyInto(out *OIDCClientTokenLifetimes) { + *out = *in + if in.IDTokenSeconds != nil { + in, out := &in.IDTokenSeconds, &out.IDTokenSeconds + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCClientTokenLifetimes. +func (in *OIDCClientTokenLifetimes) DeepCopy() *OIDCClientTokenLifetimes { + if in == nil { + return nil + } + out := new(OIDCClientTokenLifetimes) + in.DeepCopyInto(out) + return out +} diff --git a/internal/crud/crud.go b/internal/crud/crud.go index e3212fd5c..03aff68e8 100644 --- a/internal/crud/crud.go +++ b/internal/crud/crud.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package crud @@ -40,7 +40,7 @@ const ( ) type Storage interface { - Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference) (resourceVersion string, err error) + Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference, lifetime time.Duration) (resourceVersion string, err error) Get(ctx context.Context, signature string, data JSON) (resourceVersion string, err error) Update(ctx context.Context, signature, resourceVersion string, data JSON) (newResourceVersion string, err error) Delete(ctx context.Context, signature string) error @@ -50,13 +50,12 @@ type Storage interface { type JSON interface{} // document that we need valid JSON types -func New(resource string, secrets corev1client.SecretInterface, clock func() time.Time, lifetime time.Duration) Storage { +func New(resource string, secrets corev1client.SecretInterface, clock func() time.Time) Storage { return &secretsStorage{ resource: resource, secretType: secretType(resource), secrets: secrets, clock: clock, - lifetime: lifetime, } } @@ -65,11 +64,10 @@ type secretsStorage struct { secretType corev1.SecretType secrets corev1client.SecretInterface clock func() time.Time - lifetime time.Duration } -func (s *secretsStorage) Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference) (string, error) { - secret, err := s.toSecret(signature, "", data, additionalLabels, ownerReferences) +func (s *secretsStorage) Create(ctx context.Context, signature string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference, lifetime time.Duration) (string, error) { + secret, err := s.toSecret(signature, "", data, additionalLabels, ownerReferences, lifetime) if err != nil { return "", err } @@ -96,7 +94,7 @@ func (s *secretsStorage) Get(ctx context.Context, signature string, data JSON) ( // Update takes a resourceVersion because it assumes Get has been recently called to obtain the latest resource version. // This is to ensure that concurrent edits are treated as conflict errors (only one will win). func (s *secretsStorage) Update(ctx context.Context, signature, resourceVersion string, data JSON) (string, error) { - secret, err := s.toSecret(signature, resourceVersion, data, nil, nil) + secret, err := s.toSecret(signature, resourceVersion, data, nil, nil, 0) if err != nil { return "", err } @@ -189,7 +187,7 @@ func (s *secretsStorage) GetName(signature string) string { return fmt.Sprintf(secretNameFormat, s.resource, signatureAsValidName) } -func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference) (*corev1.Secret, error) { +func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON, additionalLabels map[string]string, ownerReferences []metav1.OwnerReference, lifetime time.Duration) (*corev1.Secret, error) { buf, err := json.Marshal(data) if err != nil { return nil, fmt.Errorf("failed to encode secret data for %s: %w", s.GetName(signature), err) @@ -202,9 +200,9 @@ func (s *secretsStorage) toSecret(signature, resourceVersion string, data JSON, labelsToAdd[SecretLabelKey] = s.resource // make it easier to find this stuff via kubectl var annotations map[string]string - if s.lifetime > 0 { + if lifetime > 0 { annotations = map[string]string{ - SecretLifetimeAnnotationKey: s.clock().Add(s.lifetime).UTC().Format(SecretLifetimeAnnotationDateFormat), + SecretLifetimeAnnotationKey: s.clock().Add(lifetime).UTC().Format(SecretLifetimeAnnotationDateFormat), } } diff --git a/internal/federationdomain/clientregistry/clientregistry.go b/internal/federationdomain/clientregistry/clientregistry.go index 24dfaedfb..573de6fe7 100644 --- a/internal/federationdomain/clientregistry/clientregistry.go +++ b/internal/federationdomain/clientregistry/clientregistry.go @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package clientregistry defines Pinniped's OAuth2/OIDC clients. @@ -27,6 +27,12 @@ import ( // or a dynamic client defined by an OIDCClient CR. type Client struct { fosite.DefaultOpenIDConnectClient + + // Optionally provide a lifetime for ID tokens that result from authcode exchanges (initial logins) + // and refresh grants for this specific client. This will not impact the lifetime of ID tokens created + // via RFC8693 token exchange. When zero, the ID token lifetime will be determined by the defaults + // for the FederationDomain. + IDTokenLifetimeConfiguration time.Duration } // Client implements the base, OIDC, and response_mode client interfaces of Fosite. @@ -165,10 +171,19 @@ func PinnipedCLI() *Client { TokenEndpointAuthSigningAlgorithm: coreosoidc.RS256, TokenEndpointAuthMethod: "none", }, + IDTokenLifetimeConfiguration: 0, // never override the default timeouts for this client } } func oidcClientCRToFositeClient(oidcClient *configv1alpha1.OIDCClient, clientSecrets []string) *Client { + // Allow the user to optionally override the default timeouts for these clients. + idTokenLifetimeOverrideInSeconds := oidcClient.Spec.TokenLifetimes.IDTokenSeconds + var idTokenLifetime time.Duration + if idTokenLifetimeOverrideInSeconds != nil { + // It should be safe to cast this int32 to time.Duration, because time.Duration is an int64. + idTokenLifetime = time.Duration(*(oidcClient.Spec.TokenLifetimes.IDTokenSeconds)) * time.Second + } + return &Client{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{ DefaultClient: &fosite.DefaultClient{ @@ -192,6 +207,7 @@ func oidcClientCRToFositeClient(oidcClient *configv1alpha1.OIDCClient, clientSec TokenEndpointAuthSigningAlgorithm: coreosoidc.RS256, TokenEndpointAuthMethod: "client_secret_basic", }, + IDTokenLifetimeConfiguration: idTokenLifetime, } } diff --git a/internal/federationdomain/endpoints/token/token_handler.go b/internal/federationdomain/endpoints/token/token_handler.go index bcfd31fc2..e441f6ce6 100644 --- a/internal/federationdomain/endpoints/token/token_handler.go +++ b/internal/federationdomain/endpoints/token/token_handler.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/ory/fosite" errorsx "github.com/pkg/errors" @@ -19,8 +20,10 @@ import ( oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc" "go.pinniped.dev/internal/federationdomain/federationdomainproviders" + "go.pinniped.dev/internal/federationdomain/idtokenlifespan" "go.pinniped.dev/internal/federationdomain/oidc" "go.pinniped.dev/internal/federationdomain/resolvedprovider" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/idtransform" "go.pinniped.dev/internal/plog" @@ -30,6 +33,8 @@ import ( func NewHandler( idpLister federationdomainproviders.FederationDomainIdentityProvidersListerI, oauthHelper fosite.OAuth2Provider, + overrideAccessTokenLifespan timeouts.OverrideLifespan, + overrideIDTokenLifespan timeouts.OverrideLifespan, ) http.Handler { return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { session := psession.NewPinnipedSession() @@ -66,7 +71,17 @@ func NewHandler( } } - accessResponse, err := oauthHelper.NewAccessResponse(r.Context(), accessRequest) + // Lifetimes of the access and refresh tokens are determined by the above call to NewAccessRequest. + // Depending on the request, sometimes override the default access token lifespan. + maybeOverrideDefaultAccessTokenLifetime(overrideAccessTokenLifespan, accessRequest) + + // Create the token response. + // The lifetime of the ID token will be determined inside the call NewAccessResponse. + // Depending on the request, sometimes override the default ID token lifespan by putting + // the override value onto the context. + accessResponse, err := oauthHelper.NewAccessResponse( + maybeOverrideDefaultIDTokenLifetime(r.Context(), overrideIDTokenLifespan, accessRequest), + accessRequest) if err != nil { plog.Info("token response error", oidc.FositeErrorForLog(err)...) oauthHelper.WriteAccessError(r.Context(), w, accessRequest, err) @@ -79,6 +94,19 @@ func NewHandler( }) } +func maybeOverrideDefaultAccessTokenLifetime(overrideAccessTokenLifespan timeouts.OverrideLifespan, accessRequest fosite.AccessRequester) { + if doOverride, newLifespan := overrideAccessTokenLifespan(accessRequest); doOverride { + accessRequest.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().UTC().Add(newLifespan).Round(time.Second)) + } +} + +func maybeOverrideDefaultIDTokenLifetime(baseCtx context.Context, overrideIDTokenLifespan timeouts.OverrideLifespan, accessRequest fosite.AccessRequester) context.Context { + if doOverride, newLifespan := overrideIDTokenLifespan(accessRequest); doOverride { + return idtokenlifespan.OverrideIDTokenLifespanInContext(baseCtx, newLifespan) + } + return baseCtx +} + func errMissingUpstreamSessionInternalError() *fosite.RFC6749Error { return &fosite.RFC6749Error{ ErrorField: "error", diff --git a/internal/federationdomain/endpointsmanager/manager.go b/internal/federationdomain/endpointsmanager/manager.go index 55b6ceca6..ea5f6e016 100644 --- a/internal/federationdomain/endpointsmanager/manager.go +++ b/internal/federationdomain/endpointsmanager/manager.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package endpointsmanager @@ -161,6 +161,8 @@ func (m *Manager) SetFederationDomains(federationDomains ...*federationdomainpro m.providerHandlers[(issuerHostWithPath + oidc.TokenEndpointPath)] = token.NewHandler( idpLister, oauthHelperWithKubeStorage, + timeoutsConfiguration.OverrideDefaultAccessTokenLifespan, + timeoutsConfiguration.OverrideDefaultIDTokenLifespan, ) m.providerHandlers[(issuerHostWithPath + oidc.PinnipedLoginPath)] = login.NewHandler( diff --git a/internal/federationdomain/idtokenlifespan/idtoken_lifespan.go b/internal/federationdomain/idtokenlifespan/idtoken_lifespan.go new file mode 100644 index 000000000..919d41a99 --- /dev/null +++ b/internal/federationdomain/idtokenlifespan/idtoken_lifespan.go @@ -0,0 +1,55 @@ +// Copyright 2024 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package idtokenlifespan + +import ( + "context" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/handler/openid" +) + +// contextKey type is unexported to prevent collisions. +type contextKey int + +const idTokenLifetimeOverrideKey contextKey = iota + +// OpenIDConnectExplicitFactory is similar to the function of the same name in the fosite compose package, +// except it allows wrapping the IDTokenLifespanProvider. +func OpenIDConnectExplicitFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + openIDConnectExplicitHandler := compose.OpenIDConnectExplicitFactory(config, storage, strategy).(*openid.OpenIDConnectExplicitHandler) + // Overwrite the config with a wrapper around the fosite.IDTokenLifespanProvider. + openIDConnectExplicitHandler.Config = &contextAwareIDTokenLifespanProvider{DelegateConfig: config} + return openIDConnectExplicitHandler +} + +// OpenIDConnectRefreshFactory is similar to the function of the same name in the fosite compose package, +// except it allows wrapping the IDTokenLifespanProvider. +func OpenIDConnectRefreshFactory(config fosite.Configurator, _ interface{}, strategy interface{}) interface{} { + openIDConnectRefreshHandler := compose.OpenIDConnectRefreshFactory(config, nil, strategy).(*openid.OpenIDConnectRefreshHandler) + // Overwrite the config with a wrapper around the fosite.IDTokenLifespanProvider. + openIDConnectRefreshHandler.Config = &contextAwareIDTokenLifespanProvider{DelegateConfig: config} + return openIDConnectRefreshHandler +} + +var _ fosite.IDTokenLifespanProvider = (*contextAwareIDTokenLifespanProvider)(nil) + +type contextAwareIDTokenLifespanProvider struct { + DelegateConfig fosite.IDTokenLifespanProvider +} + +func (c *contextAwareIDTokenLifespanProvider) GetIDTokenLifespan(ctx context.Context) time.Duration { + idTokenLifespanOverride, ok := ctx.Value(idTokenLifetimeOverrideKey).(time.Duration) + if ok { + return idTokenLifespanOverride + } + // When there is no override on the context, just return the default by calling the delegate. + return c.DelegateConfig.GetIDTokenLifespan(ctx) +} + +func OverrideIDTokenLifespanInContext(ctx context.Context, newLifespan time.Duration) context.Context { + return context.WithValue(ctx, idTokenLifetimeOverrideKey, newLifespan) +} diff --git a/internal/federationdomain/oidc/oidc.go b/internal/federationdomain/oidc/oidc.go index dddee951e..7715f9e89 100644 --- a/internal/federationdomain/oidc/oidc.go +++ b/internal/federationdomain/oidc/oidc.go @@ -7,8 +7,10 @@ package oidc import ( "crypto/subtle" + "errors" "fmt" "net/http" + "reflect" "time" "github.com/felixge/httpsnoop" @@ -17,10 +19,12 @@ import ( errorsx "github.com/pkg/errors" oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc" + "go.pinniped.dev/internal/federationdomain/clientregistry" "go.pinniped.dev/internal/federationdomain/csrftoken" "go.pinniped.dev/internal/federationdomain/endpoints/jwks" "go.pinniped.dev/internal/federationdomain/endpoints/tokenexchange" "go.pinniped.dev/internal/federationdomain/formposthtml" + "go.pinniped.dev/internal/federationdomain/idtokenlifespan" "go.pinniped.dev/internal/federationdomain/strategy" "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/httputil/httperr" @@ -102,23 +106,121 @@ type UpstreamStateParamData struct { FormatVersion string `json:"v"` } -// Get the defaults for the Supervisor server. +// DefaultOIDCTimeoutsConfiguration returns the default timeouts for the Supervisor server. func DefaultOIDCTimeoutsConfiguration() timeouts.Configuration { - accessTokenLifespan := 2 * time.Minute + // Note: The maximum time that users can access Kubernetes clusters without + // needing to do a Supervisor refresh is the sum of the access token lifetime, + // the ID token lifetime, and the Concierge's mTLS client cert lifetime. + // This is because a client can exchange the access token just before it expires + // for a new cluster-scoped ID token, and use that just before it expires to get + // a new mTLS client cert, which grants access to the cluster until it expires. + // + // Note that the Concierge's mTLS client cert lifetime is 5 minutes, which can + // be seen in its source at credentialrequest/rest.go. + // + // This maximum total time is important because it represents the longest possible + // time that a user could continue to use a cluster based on their original login + // (or most recent refresh) after an administrator of an external identity provider + // removes the user, revokes their session, changes their group membership, + // or otherwise makes any type of change to the user's account in the external + // identity provider that should be noticed by the Supervisor during an upstream + // refresh. + // + // Given the timeouts specified below, this is: 2 + 2 + 5 = 9 minutes. + // Note that this may be different if an OIDCClient's configuration has changed + // the lifetime of the ID tokens issued to that client, but usually will not be + // different because that configuration does not change the lifetime of the + // cluster-scoped ID tokens. The only case where that configuration would change + // it is if the admin configured a cluster to accept the initial ID token's + // audience instead of the cluster-scoped ID token's audience. + // + // The CLI will use a cached mTLS client cert until it expires. + // Because of the default timeouts, when the first mTLS client cert expires after + // five minutes, the CLI will need to perform a refresh before it can get a second + // client cert, due to the original access token and cluster-scoped ID token having + // already expired by that time (after two minutes). + + // Give a generous amount of time for an authorized client to be able to exchange + // its authcode for tokens. authorizationCodeLifespan := 10 * time.Minute + + // This is intended to give a very short amount of time to allow the client to + // use the access token to exchange for cluster-scoped ID token(s). After this + // time runs out, they will need to perform a refresh to get a new tokens, + // ensuring the Supervisor has a chance to revalidate their session often. + accessTokenLifespan := 2 * time.Minute + + // The ID token will have the same default lifespan as the access token for a + // similar reason. This is the default lifespan for ID tokens issued by the + // authcode flow, the refresh flow, and the cluster-scoped token exchange. + // The cluster-scoped ID token can be exchanged for an mTLS client cert, so + // limit the window of opportunity to make that exchange to be small. + idTokenLifespan := accessTokenLifespan + + // This is just long enough to cover a typical work day, giving the end user an + // experience of logging in once per day to access all their Kubernetes clusters. refreshTokenLifespan := 9 * time.Hour + // Give a little extra time for some storage lifetimes, to avoid the possibility + // that the storage be garbage collected in the middle of trying to look up the token. + storageExtraLifetime := time.Minute + return timeouts.Configuration{ - UpstreamStateParamLifespan: 90 * time.Minute, - AuthorizeCodeLifespan: authorizationCodeLifespan, - AccessTokenLifespan: accessTokenLifespan, - IDTokenLifespan: accessTokenLifespan, - RefreshTokenLifespan: refreshTokenLifespan, - AuthorizationCodeSessionStorageLifetime: authorizationCodeLifespan + refreshTokenLifespan, - PKCESessionStorageLifetime: authorizationCodeLifespan + (1 * time.Minute), - OIDCSessionStorageLifetime: authorizationCodeLifespan + (1 * time.Minute), - AccessTokenSessionStorageLifetime: refreshTokenLifespan + accessTokenLifespan, - RefreshTokenSessionStorageLifetime: refreshTokenLifespan + accessTokenLifespan, + // Give enough time for someone to start an interactive authorization flow, go eat lunch, + // and then finish the authorization afterward. + UpstreamStateParamLifespan: 90 * time.Minute, + + AuthorizeCodeLifespan: authorizationCodeLifespan, + + AccessTokenLifespan: accessTokenLifespan, + OverrideDefaultAccessTokenLifespan: func(accessRequest fosite.AccessRequester) (bool, time.Duration) { + // Not currently overriding the defaults. + return false, 0 + }, + + IDTokenLifespan: idTokenLifespan, + OverrideDefaultIDTokenLifespan: func(accessRequest fosite.AccessRequester) (bool, time.Duration) { + client := accessRequest.GetClient() + // Don't allow OIDCClients to override the default lifetime for ID tokens returned + // by RFC8693 token exchange. This is not user configurable for now. + if !accessRequest.GetGrantTypes().ExactOne(oidcapi.GrantTypeTokenExchange) { + if castClient, ok := client.(*clientregistry.Client); !ok { + // All clients returned by our client registry implement clientregistry.Client, + // so this should be a safe cast in practice. + plog.Error("could not check if client overrides token lifetimes", + errors.New("could not cast client to *clientregistry.Client"), + "clientID", client.GetID(), "clientType", reflect.TypeOf(client)) + } else if castClient.IDTokenLifetimeConfiguration > 0 { + // An OIDCClient resource has provided an override, so use it. + // Note that the pinniped-cli client never overrides this value. + return true, castClient.IDTokenLifetimeConfiguration + } + } + // Otherwise, do not override the defaults. + return false, 0 + }, + + RefreshTokenLifespan: refreshTokenLifespan, + + AuthorizationCodeSessionStorageLifetime: func(requester fosite.Requester) time.Duration { + return authorizationCodeLifespan + refreshTokenLifespan + }, + + PKCESessionStorageLifetime: func(_requester fosite.Requester) time.Duration { + return authorizationCodeLifespan + storageExtraLifetime + }, + + OIDCSessionStorageLifetime: func(_requester fosite.Requester) time.Duration { + return authorizationCodeLifespan + storageExtraLifetime + }, + + AccessTokenSessionStorageLifetime: func(requester fosite.Requester) time.Duration { + return refreshTokenLifespan + accessTokenLifespan + }, + + RefreshTokenSessionStorageLifetime: func(requester fosite.Requester) time.Duration { + return refreshTokenLifespan + accessTokenLifespan + }, } } @@ -170,8 +272,10 @@ func FositeOauth2Helper( }, compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2RefreshTokenGrantFactory, - compose.OpenIDConnectExplicitFactory, - compose.OpenIDConnectRefreshFactory, + // Use a custom factory to allow selective overrides of the ID token lifespan during authcode exchange. + idtokenlifespan.OpenIDConnectExplicitFactory, + // Use a custom factory to allow selective overrides of the ID token lifespan during refresh. + idtokenlifespan.OpenIDConnectRefreshFactory, compose.OAuth2PKCEFactory, tokenexchange.HandlerFactory, // handle the "urn:ietf:params:oauth:grant-type:token-exchange" grant type ) @@ -341,8 +445,8 @@ func rewriteStatusSeeOtherToStatusFoundForBrowserless(w http.ResponseWriter) htt // https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11 // Safari has the bad behavior in the case of http.StatusFound and not just http.StatusTemporaryRedirect. // - // in the browserless flows, the OAuth client is the pinniped CLI and it already has access to the user's - // password. Thus there is no security issue with using http.StatusFound vs. http.StatusSeeOther. + // In the browserless flows, the OAuth client is the pinniped CLI, and it already has access to the user's + // password. Thus, there is no security issue with using http.StatusFound vs. http.StatusSeeOther. return httpsnoop.Wrap(w, httpsnoop.Hooks{ WriteHeader: func(delegate httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { return func(code int) { diff --git a/internal/federationdomain/timeouts/timeouts_configuration.go b/internal/federationdomain/timeouts/timeouts_configuration.go index cd55cfde6..639051b15 100644 --- a/internal/federationdomain/timeouts/timeouts_configuration.go +++ b/internal/federationdomain/timeouts/timeouts_configuration.go @@ -3,7 +3,18 @@ package timeouts -import "time" +import ( + "time" + + "github.com/ory/fosite" +) + +// StorageLifetime is a function that can, given a request, decide how long it should live in session storage. +type StorageLifetime func(requester fosite.Requester) time.Duration + +// OverrideLifespan is a function that, given a request, can suggest to override the default lifespan +// by returning true along with a new lifespan. When false is returned, the returned duration should be ignored. +type OverrideLifespan func(accessRequest fosite.AccessRequester) (bool, time.Duration) type Configuration struct { // The length of time that our state param that we encrypt and pass to the upstream OIDC IDP should be considered @@ -22,11 +33,28 @@ type Configuration struct { // be fairly short-lived. AccessTokenLifespan time.Duration + // Optionally override the default AccessTokenLifespan depending on the specific request. + // Note that access tokens can be issued by authcode exchanges and refreshes (with different grant types on the + // request), so implementations of this method should handle choosing lifespans for both cases as desired. + // Note that fosite offers the fosite.ClientWithCustomTokenLifespans interface, but that interface does not + // pass the full request details to the GetEffectiveLifespan() function, so it does not suit our needs, + // and we use this technique instead. + OverrideDefaultAccessTokenLifespan OverrideLifespan + // The lifetime of an downstream ID token issued by the token endpoint. This should generally be the same // as the AccessTokenLifespan, or longer if it would be useful for the user's proof of identity to be valid // for longer than their proof of authorization. IDTokenLifespan time.Duration + // Optionally override the default IDTokenLifespan depending on the specific request. + // Note that ID tokens can be issued by authcode exchanges, refreshes, and RFC8693 token exchanges + // (with different grant types on the request), so implementations of this method should handle choosing + // lifespans for all three cases as desired. + // Note that fosite offers the fosite.ClientWithCustomTokenLifespans interface, but that interface does not + // pass the full request details to the GetEffectiveLifespan() function, so it does not suit our needs, + // and we use this technique instead. + OverrideDefaultIDTokenLifespan OverrideLifespan + // The lifetime of an downstream refresh token issued by the token endpoint. This should generally be // significantly longer than the access token lifetime, so it can be used to refresh the access token // multiple times. Once the refresh token expires, the user's session is over and they will need @@ -40,7 +68,7 @@ type Configuration struct { // include revoking the access and refresh tokens associated with the session. Therefore, this should be // significantly longer than the AuthorizeCodeLifespan, and there is probably no reason to make it longer than // the sum of the AuthorizeCodeLifespan and the RefreshTokenLifespan. - AuthorizationCodeSessionStorageLifetime time.Duration + AuthorizationCodeSessionStorageLifetime StorageLifetime // PKCESessionStorageLifetime is the length of time after which PKCE data is allowed to be garbage collected from // storage. PKCE sessions are closely related to authorization code sessions. After the authcode is successfully @@ -48,19 +76,19 @@ type Configuration struct { // but it is not explicitly deleted. Therefore, this can be just slightly longer than the AuthorizeCodeLifespan. We'll // avoid making it exactly the same as AuthorizeCodeLifespan to avoid any chance of the garbage collector deleting it // while it is being used. - PKCESessionStorageLifetime time.Duration + PKCESessionStorageLifetime StorageLifetime // OIDCSessionStorageLifetime is the length of time after which the OIDC session data related to an authcode // is allowed to be garbage collected from storage. After the authcode is successfully redeemed, the OIDC session is // explicitly deleted. Similar to the PKCE session, they are not needed anymore after the corresponding authcode has expired. // Therefore, this can be just slightly longer than the AuthorizeCodeLifespan. We'll avoid making it exactly the same // as AuthorizeCodeLifespan to avoid any chance of the garbage collector deleting it while it is being used. - OIDCSessionStorageLifetime time.Duration + OIDCSessionStorageLifetime StorageLifetime // AccessTokenSessionStorageLifetime is the length of time after which an access token's session data is allowed // to be garbage collected from storage. These must exist in storage for as long as the refresh token is valid // or else the refresh flow will not work properly. So this must be longer than RefreshTokenLifespan. - AccessTokenSessionStorageLifetime time.Duration + AccessTokenSessionStorageLifetime StorageLifetime // RefreshTokenSessionStorageLifetime is the length of time after which a refresh token's session data is allowed // to be garbage collected from storage. These must exist in storage for as long as the refresh token is valid. @@ -70,5 +98,5 @@ type Configuration struct { // error message telling them that the token is expired, rather than a more generic error that is returned // when the token does not exist. If this is desirable, then the RefreshTokenSessionStorageLifetime can be made // to be significantly larger than RefreshTokenLifespan, at the cost of slower cleanup. - RefreshTokenSessionStorageLifetime time.Duration + RefreshTokenSessionStorageLifetime StorageLifetime } diff --git a/internal/fositestorage/accesstoken/accesstoken.go b/internal/fositestorage/accesstoken/accesstoken.go index 9a5ca87a7..247273c42 100644 --- a/internal/fositestorage/accesstoken/accesstoken.go +++ b/internal/fositestorage/accesstoken/accesstoken.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package accesstoken @@ -17,6 +17,7 @@ import ( "go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/federationdomain/clientregistry" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/psession" ) @@ -44,7 +45,8 @@ type RevocationStorage interface { var _ RevocationStorage = &accessTokenStorage{} type accessTokenStorage struct { - storage crud.Storage + storage crud.Storage + lifetime timeouts.StorageLifetime } type Session struct { @@ -52,8 +54,8 @@ type Session struct { Version string `json:"version"` } -func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) RevocationStorage { - return &accessTokenStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)} +func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) RevocationStorage { + return &accessTokenStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime} } // ReadFromSecret reads the contents of a Secret as a Session. @@ -83,12 +85,12 @@ func (a *accessTokenStorage) CreateAccessTokenSession(ctx context.Context, signa return err } - _, err = a.storage.Create( - ctx, + _, err = a.storage.Create(ctx, signature, &Session{Request: request, Version: accessTokenStorageVersion}, map[string]string{fositestorage.StorageRequestIDLabelName: requester.GetID()}, nil, + a.lifetime(requester), ) return err } diff --git a/internal/fositestorage/authorizationcode/authorizationcode.go b/internal/fositestorage/authorizationcode/authorizationcode.go index 41635a7ad..c71f0188b 100644 --- a/internal/fositestorage/authorizationcode/authorizationcode.go +++ b/internal/fositestorage/authorizationcode/authorizationcode.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package authorizationcode @@ -18,6 +18,7 @@ import ( "go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/federationdomain/clientregistry" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/psession" ) @@ -40,7 +41,8 @@ const ( var _ oauth2.AuthorizeCodeStorage = &authorizeCodeStorage{} type authorizeCodeStorage struct { - storage crud.Storage + storage crud.Storage + lifetime timeouts.StorageLifetime } type Session struct { @@ -49,8 +51,8 @@ type Session struct { Version string `json:"version"` } -func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) oauth2.AuthorizeCodeStorage { - return &authorizeCodeStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)} +func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) oauth2.AuthorizeCodeStorage { + return &authorizeCodeStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime} } // ReadFromSecret reads the contents of a Secret as a Session. @@ -92,7 +94,13 @@ func (a *authorizeCodeStorage) CreateAuthorizeCodeSession(ctx context.Context, s // of the consent authorization request. It is used to identify the session. // signature for lookup in the DB - _, err = a.storage.Create(ctx, signature, &Session{Active: true, Request: request, Version: authorizeCodeStorageVersion}, nil, nil) + _, err = a.storage.Create(ctx, + signature, + &Session{Active: true, Request: request, Version: authorizeCodeStorageVersion}, + nil, + nil, + a.lifetime(requester), + ) return err } diff --git a/internal/fositestorage/openidconnect/openidconnect.go b/internal/fositestorage/openidconnect/openidconnect.go index 466f0f2e9..58e560a10 100644 --- a/internal/fositestorage/openidconnect/openidconnect.go +++ b/internal/fositestorage/openidconnect/openidconnect.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package openidconnect @@ -17,6 +17,7 @@ import ( "go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/federationdomain/clientregistry" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/psession" ) @@ -40,7 +41,8 @@ const ( var _ openid.OpenIDConnectRequestStorage = &openIDConnectRequestStorage{} type openIDConnectRequestStorage struct { - storage crud.Storage + storage crud.Storage + lifetime timeouts.StorageLifetime } type session struct { @@ -48,8 +50,8 @@ type session struct { Version string `json:"version"` } -func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) openid.OpenIDConnectRequestStorage { - return &openIDConnectRequestStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)} +func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) openid.OpenIDConnectRequestStorage { + return &openIDConnectRequestStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime} } func (a *openIDConnectRequestStorage) CreateOpenIDConnectSession(ctx context.Context, authcode string, requester fosite.Requester) error { @@ -63,7 +65,13 @@ func (a *openIDConnectRequestStorage) CreateOpenIDConnectSession(ctx context.Con return err } - _, err = a.storage.Create(ctx, signature, &session{Request: request, Version: oidcStorageVersion}, nil, nil) + _, err = a.storage.Create(ctx, + signature, + &session{Request: request, Version: oidcStorageVersion}, + nil, + nil, + a.lifetime(requester), + ) return err } diff --git a/internal/fositestorage/pkce/pkce.go b/internal/fositestorage/pkce/pkce.go index 78fe0d380..9b6a14d4c 100644 --- a/internal/fositestorage/pkce/pkce.go +++ b/internal/fositestorage/pkce/pkce.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package pkce @@ -16,6 +16,7 @@ import ( "go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/federationdomain/clientregistry" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/psession" ) @@ -38,7 +39,8 @@ const ( var _ pkce.PKCERequestStorage = &pkceStorage{} type pkceStorage struct { - storage crud.Storage + storage crud.Storage + lifetime timeouts.StorageLifetime } type session struct { @@ -46,8 +48,8 @@ type session struct { Version string `json:"version"` } -func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) pkce.PKCERequestStorage { - return &pkceStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)} +func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) pkce.PKCERequestStorage { + return &pkceStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime} } func (a *pkceStorage) CreatePKCERequestSession(ctx context.Context, signature string, requester fosite.Requester) error { @@ -56,7 +58,13 @@ func (a *pkceStorage) CreatePKCERequestSession(ctx context.Context, signature st return err } - _, err = a.storage.Create(ctx, signature, &session{Request: request, Version: pkceStorageVersion}, nil, nil) + _, err = a.storage.Create(ctx, + signature, + &session{Request: request, Version: pkceStorageVersion}, + nil, + nil, + a.lifetime(requester), + ) return err } diff --git a/internal/fositestorage/refreshtoken/refreshtoken.go b/internal/fositestorage/refreshtoken/refreshtoken.go index 8f947fcfb..d87c6bb9b 100644 --- a/internal/fositestorage/refreshtoken/refreshtoken.go +++ b/internal/fositestorage/refreshtoken/refreshtoken.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package refreshtoken @@ -17,6 +17,7 @@ import ( "go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/federationdomain/clientregistry" + "go.pinniped.dev/internal/federationdomain/timeouts" "go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/psession" ) @@ -45,7 +46,8 @@ type RevocationStorage interface { var _ RevocationStorage = &refreshTokenStorage{} type refreshTokenStorage struct { - storage crud.Storage + storage crud.Storage + lifetime timeouts.StorageLifetime } type Session struct { @@ -53,8 +55,8 @@ type Session struct { Version string `json:"version"` } -func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime time.Duration) RevocationStorage { - return &refreshTokenStorage{storage: crud.New(TypeLabelValue, secrets, clock, sessionStorageLifetime)} +func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) RevocationStorage { + return &refreshTokenStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime} } // ReadFromSecret reads the contents of a Secret as a Session. @@ -89,12 +91,12 @@ func (a *refreshTokenStorage) CreateRefreshTokenSession(ctx context.Context, sig return err } - _, err = a.storage.Create( - ctx, + _, err = a.storage.Create(ctx, signature, &Session{Request: request, Version: refreshTokenStorageVersion}, map[string]string{fositestorage.StorageRequestIDLabelName: requester.GetID()}, nil, + a.lifetime(requester), ) return err } diff --git a/internal/oidcclientsecretstorage/oidcclientsecretstorage.go b/internal/oidcclientsecretstorage/oidcclientsecretstorage.go index ae4788d55..1cdba6549 100644 --- a/internal/oidcclientsecretstorage/oidcclientsecretstorage.go +++ b/internal/oidcclientsecretstorage/oidcclientsecretstorage.go @@ -1,4 +1,4 @@ -// Copyright 2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package oidcclientsecretstorage @@ -45,7 +45,7 @@ type storedClientSecret struct { func New(secrets corev1client.SecretInterface) *OIDCClientSecretStorage { return &OIDCClientSecretStorage{ - storage: crud.New(TypeLabelValue, secrets, nil, 0), // can use nil clock because we are using infinite lifetime + storage: crud.New(TypeLabelValue, secrets, nil), // can use nil clock because we are using infinite lifetime for creates secrets: secrets, } } @@ -91,7 +91,7 @@ func (s *OIDCClientSecretStorage) Set(ctx context.Context, resourceVersion, oidc Controller: nil, // doesn't seem to matter, and there is no particular controller owning this BlockOwnerDeletion: nil, }} - if _, err := s.storage.Create(ctx, name, secret, nil, ownerReferences); err != nil { + if _, err := s.storage.Create(ctx, name, secret, nil, ownerReferences, 0); err != nil { // 0 is infinite lifetime return fmt.Errorf("failed to create client secret for uid %s: %w", oidcClientUID, err) } return nil diff --git a/test/integration/supervisor_oidc_client_test.go b/test/integration/supervisor_oidc_client_test.go index 77f659b63..f76d380fc 100644 --- a/test/integration/supervisor_oidc_client_test.go +++ b/test/integration/supervisor_oidc_client_test.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -6,6 +6,7 @@ package integration import ( "context" "fmt" + "k8s.io/utils/ptr" "sort" "strings" "testing" @@ -155,6 +156,54 @@ func TestOIDCClientStaticValidation_Parallel(t *testing.T) { }, 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: "ID token lifetime too small", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-hello", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + TokenLifetimes: supervisorconfigv1alpha1.OIDCClientTokenLifetimes{ + IDTokenSeconds: ptr.To[int32](119), + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-hello" is invalid: ` + + `spec.tokenLifetimes.idTokenSeconds: Invalid value: 119: spec.tokenLifetimes.idTokenSeconds in body should be greater than or equal to 120`, + }, + { + name: "ID token lifetime too large", + client: &supervisorconfigv1alpha1.OIDCClient{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client.oauth.pinniped.dev-hello", + }, + Spec: supervisorconfigv1alpha1.OIDCClientSpec{ + AllowedRedirectURIs: []supervisorconfigv1alpha1.RedirectURI{ + "http://127.0.0.1/callback", + }, + AllowedGrantTypes: []supervisorconfigv1alpha1.GrantType{ + "refresh_token", + }, + AllowedScopes: []supervisorconfigv1alpha1.Scope{ + "username", + }, + TokenLifetimes: supervisorconfigv1alpha1.OIDCClientTokenLifetimes{ + IDTokenSeconds: ptr.To[int32](1801), + }, + }, + }, + wantErr: `OIDCClient.config.supervisor.pinniped.dev "client.oauth.pinniped.dev-hello" is invalid: ` + + `spec.tokenLifetimes.idTokenSeconds: Invalid value: 1801: spec.tokenLifetimes.idTokenSeconds in body should be less than or equal to 1800`, + }, { name: "bad grant type", client: &supervisorconfigv1alpha1.OIDCClient{