proposal: bound service account tokens
Proposal to improve the utility of service account tokens by supporting audience, time and key binding. @kubernetes/sig-auth-api-reviews
This commit is contained in:
parent
1f79ecc4a4
commit
6e209490c4
|
@ -0,0 +1,239 @@
|
||||||
|
# Bound Service Account Tokens
|
||||||
|
|
||||||
|
Author: @mikedanese
|
||||||
|
|
||||||
|
# Objective
|
||||||
|
|
||||||
|
This document describes an API that would allow workloads running on Kubernetes
|
||||||
|
to request JSON Web Tokens that are audience, time and eventually key bound.
|
||||||
|
|
||||||
|
# Background
|
||||||
|
|
||||||
|
Kubernetes already provisions JWTs to workloads. This functionality is on by
|
||||||
|
default and thus widely deployed. The current workload JWT system has serious
|
||||||
|
issues:
|
||||||
|
|
||||||
|
1. Security: JWTs are not audience bound. Any recipient of a JWT can masquerade
|
||||||
|
as the presenter to anyone else.
|
||||||
|
1. Security: The current model of storing the service account token in a Secret
|
||||||
|
and delivering it to nodes results in a broad attack surface for the
|
||||||
|
Kubernetes control plane when powerful components are run - giving a service
|
||||||
|
account a permission means that any component that can see that service
|
||||||
|
account's secrets is at least as powerful as the component.
|
||||||
|
1. Security: JWTs are not time bound. A JWT compromised via 1 or 2, is valid
|
||||||
|
for as long as the service account exists. This may be mitigated with
|
||||||
|
service account signing key rotation but is not supported by client-go and
|
||||||
|
not automated by the control plane and thus is not widely deployed.
|
||||||
|
1. Scalability: JWTs require a Kubernetes secret per service account.
|
||||||
|
|
||||||
|
# Proposal
|
||||||
|
|
||||||
|
Infrastructure to support on demand token requests will be implemented in the
|
||||||
|
core apiserver. Once this API exists, a client of the apiserver will request an
|
||||||
|
attenuated token for it's own use. The API will enforce required attenuations,
|
||||||
|
e.g. audience and time binding.
|
||||||
|
|
||||||
|
## Token attenuations
|
||||||
|
|
||||||
|
### Audience binding
|
||||||
|
|
||||||
|
Tokens issued from this API will be audience bound. Audience of requested tokens
|
||||||
|
will be bound by the `aud` claim. The `aud` claim is an array of strings
|
||||||
|
(usually URLs) that correspond to the intended audience of the token. A
|
||||||
|
recipient of a token is responsible for verifying that it identifies as one of
|
||||||
|
the values in the audience claim, and should otherwise reject the token. The
|
||||||
|
TokenReview API will support this validation.
|
||||||
|
|
||||||
|
### Time binding
|
||||||
|
|
||||||
|
Tokens issued from this API will be time bound. Time validity of these tokens
|
||||||
|
will be claimed in the following fields:
|
||||||
|
|
||||||
|
* `exp`: expiration time
|
||||||
|
* `nbf`: not before
|
||||||
|
* `iat`: issued at
|
||||||
|
|
||||||
|
A recipient of a token should verify that the token is valid at the time that
|
||||||
|
the token is presented, and should otherwise reject the token. The TokenReview
|
||||||
|
API will support this validation.
|
||||||
|
|
||||||
|
Cluster administrators will be able to configure the maximum validity duration
|
||||||
|
for expiring tokens. During the migration off of the old service account tokens,
|
||||||
|
clients of this API may request tokens that are valid for many years. These
|
||||||
|
tokens will be drop in replacements for the current service account tokens.
|
||||||
|
|
||||||
|
### Object binding
|
||||||
|
|
||||||
|
Tokens issued from this API may be bound to a Kubernetes object in the same
|
||||||
|
namespace as the service account. The name, group, version, kind and uid of the
|
||||||
|
object will be embedded as claims in the issued token. A token bound to an
|
||||||
|
object will only be valid for as long as that object exists.
|
||||||
|
|
||||||
|
Only a subset of object kinds will support object binding. Initially the only
|
||||||
|
kinds that will be supported are:
|
||||||
|
|
||||||
|
* v1/Pod
|
||||||
|
* v1/Secret
|
||||||
|
|
||||||
|
The TokenRequest API will validate this binding.
|
||||||
|
|
||||||
|
## API Changes
|
||||||
|
|
||||||
|
### Add `tokenrequests.authentication.k8s.io`
|
||||||
|
|
||||||
|
We will add an imperative API (a la TokenReview) to the
|
||||||
|
`authentication.k8s.io` API group:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type TokenRequest struct {
|
||||||
|
Spec TokenRequestSpec
|
||||||
|
Status TokenRequestStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenRequestSpec struct {
|
||||||
|
// Audiences are the intendend audiences of the token. A token issued
|
||||||
|
// for multiple audiences may be used to authenticate against any of
|
||||||
|
// the audiences listed. This implies a high degree of trust between
|
||||||
|
// the target audiences.
|
||||||
|
Audiences []string
|
||||||
|
|
||||||
|
// ValidityDuration is the requested duration of validity of the request. The
|
||||||
|
// token issuer may return a token with a different validity duration so a
|
||||||
|
// client needs to check the 'expiration' field in a response.
|
||||||
|
ValidityDuration metav1.Duration
|
||||||
|
|
||||||
|
// BoundObjectRef is a reference to an object that the token will be bound to.
|
||||||
|
// The token will only be valid for as long as the bound object exists.
|
||||||
|
BoundObjectRef *BoundObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoundObjectReference struct {
|
||||||
|
// Kind of the referent. Valid kinds are 'Pod' and 'Secret'.
|
||||||
|
Kind string
|
||||||
|
// API version of the referent.
|
||||||
|
APIVersion string
|
||||||
|
|
||||||
|
// Name of the referent.
|
||||||
|
Name string
|
||||||
|
// UID of the referent.
|
||||||
|
UID types.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenRequestStatus struct {
|
||||||
|
// Token is the token data
|
||||||
|
Token string
|
||||||
|
|
||||||
|
// Expiration is the time of expiration of the returned token. Empty means the
|
||||||
|
// token does not expire.
|
||||||
|
Expiration metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This API will be exposed as a subresource under a serviceacccount object. A
|
||||||
|
requestor for a token for a specific service account will `POST` a
|
||||||
|
`TokenRequest` to the `/token` subresource of that service account object.
|
||||||
|
|
||||||
|
### Modify `tokenreviews.authentication.k8s.io`
|
||||||
|
|
||||||
|
The TokenReview API will be extended to support passing an additional audience
|
||||||
|
field which the service account authenticator will validate.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
type TokenReviewSpec struct {
|
||||||
|
// Token is the opaque bearer token.
|
||||||
|
Token string
|
||||||
|
// Audiences is the identifier that the client identifies as.
|
||||||
|
Audiences []string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
> POST /apis/v1/namespaces/default/serviceaccounts/default/token
|
||||||
|
> {
|
||||||
|
> "kind": "TokenRequest",
|
||||||
|
> "apiVersion": "authentication.k8s.io/v1",
|
||||||
|
> "spec": {
|
||||||
|
> "audience": [
|
||||||
|
> "https://kubernetes.default.svc"
|
||||||
|
> ],
|
||||||
|
> "validityDuration": "99999h",
|
||||||
|
> "boundObjectRef": {
|
||||||
|
> "kind": "Pod",
|
||||||
|
> "apiVersion": "v1",
|
||||||
|
> "name": "pod-foo-346acf"
|
||||||
|
> }
|
||||||
|
> }
|
||||||
|
> }
|
||||||
|
{
|
||||||
|
"kind": "TokenRequest",
|
||||||
|
"apiVersion": "authentication.k8s.io/v1",
|
||||||
|
"spec": {
|
||||||
|
"audience": [
|
||||||
|
"https://kubernetes.default.svc"
|
||||||
|
],
|
||||||
|
"validityDuration": "99999h",
|
||||||
|
"boundObjectRef": {
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"name": "pod-foo-346acf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"token":
|
||||||
|
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJz[payload omitted].EkN-[signature omitted]",
|
||||||
|
"expiration": "Jan 24 16:36:00 PST 3018"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The token payload will be:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"iss": "https://example.com/some/path",
|
||||||
|
"sub": "system:serviceaccount:default:default,
|
||||||
|
"aud": [
|
||||||
|
"https://kubernetes.default.svc"
|
||||||
|
],
|
||||||
|
"exp": 24412841114,
|
||||||
|
"iat": 1516841043,
|
||||||
|
"nbf": 1516841043,
|
||||||
|
"kubernetes.io": {
|
||||||
|
"serviceAccountUID": "c0c98eab-0168-11e8-92e5-42010af00002",
|
||||||
|
"boundObjectRef": {
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"uid": "a4bb8aa4-0168-11e8-92e5-42010af00002",
|
||||||
|
"name": "pod-foo-346acf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service Account Authenticator Modification
|
||||||
|
|
||||||
|
The service account token authenticator will be extended to support validation
|
||||||
|
of time and audience binding claims.
|
||||||
|
|
||||||
|
## ACLs for TokenRequest
|
||||||
|
|
||||||
|
The NodeAuthorizer will allow the kubelet to use its credentials to request a
|
||||||
|
service account token on behalf of pods running on that node. The
|
||||||
|
NodeRestriction admission controller will require that these tokens are pod
|
||||||
|
bound.
|
||||||
|
|
||||||
|
## Footnotes
|
||||||
|
|
||||||
|
* New apiserver flags:
|
||||||
|
* --service-account-issuer: Identifier of the issuer.
|
||||||
|
* --service-account-signing-key: Path to issuer private key used for signing.
|
||||||
|
* --service-account-api-audience: Identifier of the API. Used to validate
|
||||||
|
tokens authenticating to the Kubernetes API.
|
||||||
|
* The Kubernetes apiserver will identify itself as `kubernetes.default.svc`
|
||||||
|
which is the DNS name of the Kubernetes apiserver. When no audience is
|
||||||
|
requested, the audience is defaulted the audience is defaulted to an array
|
||||||
|
containing only this identifier.
|
||||||
|
|
Loading…
Reference in New Issue