Skip to content

Commit

Permalink
[GEP-26] Add TokenRequest API and workloadidentities/token subres…
Browse files Browse the repository at this point in the history
…ource (gardener#9813)

* Define TokenRequest API

* Generate code

* Register TokenRequest with the scheme

* Implement static validation for TokenRequest

* Register WorkloadIdentity/token subresource in apiserver

* Address review feedback

* Align GEP with the implementation

* Use long variable names

* Make (min|max)WorkloadIdentityTokenExpiration configurable

* Make workload identity issuer configurable

* Rename durationSeconds to expirationSeconds

* GOP configures --workload-identity-token-issuer flag
  • Loading branch information
vpnachev authored Jun 4, 2024
1 parent 71ea8e6 commit d80c55b
Show file tree
Hide file tree
Showing 31 changed files with 2,576 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ spec:
- --tls-private-key-file=/etc/gardener-apiserver/srv/gardener-apiserver.key
{{- end }}
{{- include "gardener-apiserver.watchCacheSizes" . | indent 8 }}
{{- if .Values.global.apiserver.workloadIdentity.token.issuer }}
- --workload-identity-token-issuer={{ .Values.global.apiserver.workloadIdentity.token.issuer }}
{{- end }}
- --workload-identity-token-min-expiration={{ .Values.global.apiserver.workloadIdentity.token.minExpiration }}
- --workload-identity-token-max-expiration={{ .Values.global.apiserver.workloadIdentity.token.maxExpiration }}
- --log-level={{ .Values.global.apiserver.logLevel | default "info" }}
- --log-format={{ .Values.global.apiserver.logFormat | default "json" }}
- --v={{ .Values.global.apiserver.logVerbosity | default "2" }}
Expand Down
5 changes: 5 additions & 0 deletions charts/gardener/controlplane/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ global:
# truncateMaxBatchSize: 10485760
# truncateMaxEventSize: 102400
# version: audit.k8s.io/v1
workloadIdentity:
token:
# issuer: https://issuer.gardener.cloud
minExpiration: 1h
maxExpiration: 48h
# Gardener admission controller configuration values
admission:
enabled: true
Expand Down
249 changes: 249 additions & 0 deletions docs/api-reference/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,81 @@ WorkloadIdentityStatus
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.ContextObject">ContextObject
</h3>
<p>
(<em>Appears on:</em>
<a href="#security.gardener.cloud/v1alpha1.TokenRequestSpec">TokenRequestSpec</a>)
</p>
<p>
<p>ContextObject identifies the object the token is requested for.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>kind</code></br>
<em>
string
</em>
</td>
<td>
<p>Kind of the object the token is requested for. Valid kinds are &lsquo;Shoot&rsquo;, &lsquo;Seed&rsquo;, etc.</p>
</td>
</tr>
<tr>
<td>
<code>apiVersion</code></br>
<em>
string
</em>
</td>
<td>
<p>API version of the object the token is requested for.</p>
</td>
</tr>
<tr>
<td>
<code>name</code></br>
<em>
string
</em>
</td>
<td>
<p>Name of the object the token is requested for.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code></br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Namespace of the object the token is requested for.</p>
</td>
</tr>
<tr>
<td>
<code>uid</code></br>
<em>
k8s.io/apimachinery/pkg/types.UID
</em>
</td>
<td>
<p>UID of the object the token is requested for.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.CredentialsBindingProvider">CredentialsBindingProvider
</h3>
<p>
Expand Down Expand Up @@ -284,6 +359,180 @@ k8s.io/apimachinery/pkg/runtime.RawExtension
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.TokenRequest">TokenRequest
</h3>
<p>
<p>TokenRequest is a resource that is used to request WorkloadIdentity tokens.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>metadata</code></br>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta">
Kubernetes meta/v1.ObjectMeta
</a>
</em>
</td>
<td>
<p>Standard object metadata.</p>
Refer to the Kubernetes API documentation for the fields of the
<code>metadata</code> field.
</td>
</tr>
<tr>
<td>
<code>spec</code></br>
<em>
<a href="#security.gardener.cloud/v1alpha1.TokenRequestSpec">
TokenRequestSpec
</a>
</em>
</td>
<td>
<p>Spec holds configuration settings for the requested token.</p>
<br/>
<br/>
<table>
<tr>
<td>
<code>contextObject</code></br>
<em>
<a href="#security.gardener.cloud/v1alpha1.ContextObject">
ContextObject
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ContextObject identifies the object the token is requested for.</p>
</td>
</tr>
<tr>
<td>
<code>expirationSeconds</code></br>
<em>
int64
</em>
</td>
<td>
<em>(Optional)</em>
<p>ExpirationSeconds specifies for how long the requested token should be valid.</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<code>status</code></br>
<em>
<a href="#security.gardener.cloud/v1alpha1.TokenRequestStatus">
TokenRequestStatus
</a>
</em>
</td>
<td>
<p>Status bears the issued token with additional information back to the client.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.TokenRequestSpec">TokenRequestSpec
</h3>
<p>
(<em>Appears on:</em>
<a href="#security.gardener.cloud/v1alpha1.TokenRequest">TokenRequest</a>)
</p>
<p>
<p>TokenRequestSpec holds configuration settings for the requested token.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>contextObject</code></br>
<em>
<a href="#security.gardener.cloud/v1alpha1.ContextObject">
ContextObject
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ContextObject identifies the object the token is requested for.</p>
</td>
</tr>
<tr>
<td>
<code>expirationSeconds</code></br>
<em>
int64
</em>
</td>
<td>
<em>(Optional)</em>
<p>ExpirationSeconds specifies for how long the requested token should be valid.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.TokenRequestStatus">TokenRequestStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#security.gardener.cloud/v1alpha1.TokenRequest">TokenRequest</a>)
</p>
<p>
<p>TokenRequestStatus bears the issued token with additional information back to the client.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>token</code></br>
<em>
string
</em>
</td>
<td>
<p>Token is the issued token.</p>
</td>
</tr>
<tr>
<td>
<code>expirationTimestamp</code></br>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#time-v1-meta">
Kubernetes meta/v1.Time
</a>
</em>
</td>
<td>
<p>ExpirationTimeStamp is the time of expiration of the returned token.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="security.gardener.cloud/v1alpha1.WorkloadIdentitySpec">WorkloadIdentitySpec
</h3>
<p>
Expand Down
38 changes: 19 additions & 19 deletions docs/proposals/26-workload-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,19 +174,19 @@ status:
JWTs will be available when the clients send `create` requests on the
`WorkloadIdentity/token` subresource. As the clients will be providing various
custom information that will be used for the generation of the JWT, yet another
resource `TokenRequest` in the API group `security.gardener.cloud` will be
used, similar to `TokenRequest` from `authentication.k8s.io/v1` API. It is
envisioned this resource to contain just metadata for the context where the JWT
is being used, e.g. shoot or backup entry identifier. Gardener API server must
verify the provided metadata and it can enhance the JWT with additional
information derived from the context, for example with information for the
project and the seed of the shoot cluster. Gardener API can also add global
information like a garden cluster identity. `TokenRequest` will feature optional
field `duration` that will allow clients to specify for how long the issued
workload identity token to be valid. This duration will be ensured to be between
certain limits of minimal and maximal validity, in order to avoid frequent token
renewals as well as tokens with too long validity. If the duration field is not
set, a default duration will be applied.
resource `TokenRequest` in the API group `security.gardener.cloud` will be used,
similar to `TokenRequest` from `authentication.k8s.io/v1` API. It is envisioned
this resource to contain just metadata for the context where the JWT is being
used, e.g. shoot or backup entry identifier. Gardener API server must verify the
provided metadata and it can enhance the JWT with additional information derived
from the context, for example with information for the project and the seed of
the shoot cluster. Gardener API can also add global information like a garden
cluster identity. `TokenRequest` will feature optional field `expirationSeconds`
that will allow clients to specify for how long the issued workload identity
token to be valid. This duration will be ensured to be between certain limits of
minimal and maximal validity, in order to avoid frequent token renewals as well
as tokens with too long validity. If the `expirationSeconds` field is not set, a
default duration of 3600 seconds will be applied.

`TokenRequest` resources will never be persisted in the storage layer, the
generated token will be written in the `.status.token` field and returned to the
Expand All @@ -203,7 +203,7 @@ spec:
name: foo
namespace: garden-local
uid: 54d09554-6a68-4f46-a23a-e3592385d820
duration: 48h # Optional field, gardener will have default value of token duration if the field is unset.
expirationSeconds: 600 # Optional field, gardener will set default value of 3600 seconds for token duration if the field is unset.
status:
token: eyJhbGciOiJ....OkBBrVWA # The generated OIDC token
expirationTimestamp: 2024-02-09T16:35:02Z
Expand Down Expand Up @@ -351,11 +351,11 @@ parameter whose value should not be the same as the issuer of the Kubernetes
service accounts. The workload identity issuer url should not be among the
accepted issuers of the Kubernetes API server. Other configuration options for
the Gardener API server will be the private key used to sign the tokens, the
minimal, maximal and the default durations for each token. The private key also
should not be shared with the Kubernetes API server. When `gardener-operator` is
used to manage the Garden cluster, it will be also responsible for the Workload
Identity token signing key rotation, a strategy similar to the one for the
Kubernetes Service Account token signing key rotation will be used.
minimal and maximal durations for each token. The private key also should not be
shared with the Kubernetes API server. When `gardener-operator` is used to
manage the Garden cluster, it will be also responsible for the Workload Identity
token signing key rotation, a strategy similar to the one for the Kubernetes
Service Account token signing key rotation will be used.

When Gardener API server is using own issuer and signing keys, the service
account token authenticator of the Kubernetes API server will reject the
Expand Down
3 changes: 3 additions & 0 deletions example/gardener-local/controlplane/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ global:
resources: {}
podLabels:
networking.resources.gardener.cloud/to-all-webhook-targets: allowed
workloadIdentity:
token:
issuer: https://issuer.gardener.cloud.local

# Gardener admission controller configuration values
admission:
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/operator/v1alpha1/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func validateRuntimeClusterUpdate(oldGarden, newGarden *operatorv1alpha1.Garden)
)

// First domain is immutable.
// Keep the first value immutable because components like the Gardener Discovery Server depend on it.
// Keep the first value immutable because components like the Gardener Discovery Server and Workload Identity depend on it.
if len(oldRuntimeCluster.Ingress.Domains) > 0 && len(newRuntimeCluster.Ingress.Domains) > 0 {
allErrs = append(allErrs, apivalidation.ValidateImmutableField(oldRuntimeCluster.Ingress.Domains[0], newRuntimeCluster.Ingress.Domains[0], fldPath.Child("ingress", "domains").Index(0))...)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/security/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&CredentialsBindingList{},
&WorkloadIdentity{},
&WorkloadIdentityList{},
&TokenRequest{},
)

return nil
Expand Down
Loading

0 comments on commit d80c55b

Please sign in to comment.