2016-02-03 22:28:06 +00:00
|
|
|
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
|
|
|
|
|
|
|
|
<!-- BEGIN STRIP_FOR_RELEASE -->
|
|
|
|
|
|
|
|
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
|
|
|
|
width="25" height="25">
|
|
|
|
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
|
|
|
|
width="25" height="25">
|
|
|
|
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
|
|
|
|
width="25" height="25">
|
|
|
|
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
|
|
|
|
width="25" height="25">
|
|
|
|
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
|
|
|
|
width="25" height="25">
|
|
|
|
|
|
|
|
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
|
|
|
|
|
|
|
|
If you are using a released version of Kubernetes, you should
|
|
|
|
refer to the docs that go with that version.
|
|
|
|
|
|
|
|
Documentation for other releases can be found at
|
|
|
|
[releases.k8s.io](http://releases.k8s.io).
|
|
|
|
</strong>
|
|
|
|
--
|
|
|
|
|
|
|
|
<!-- END STRIP_FOR_RELEASE -->
|
|
|
|
|
|
|
|
<!-- END MUNGE: UNVERSIONED_WARNING -->
|
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
# Kubelet TLS bootstrap
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
Author: George Tankersley (george.tankersley@coreos.com)
|
|
|
|
|
|
|
|
## Preface
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
This document describes a method for a kubelet to bootstrap itself
|
|
|
|
into a TLS-secured cluster. Crucially, it automates the provision and
|
|
|
|
distribution of signed certificates.
|
|
|
|
|
|
|
|
## Overview
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
When a kubelet runs for the first time, it must be given TLS assets
|
|
|
|
or generate them itself. In the first case, this is a burden on the cluster
|
|
|
|
admin and a significant logistical barrier to secure Kubernetes rollouts. In
|
|
|
|
the second, the kubelet must self-sign its certificate and forfeits many of the
|
|
|
|
advantages of a PKI system. Instead, we propose that the kubelet generate a
|
|
|
|
private key and a CSR for submission to a cluster-level certificate signing
|
|
|
|
process.
|
|
|
|
|
|
|
|
## Preliminaries
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
We assume the existence of a functioning control plane. The
|
|
|
|
apiserver should be configured for TLS initially or possess the ability to
|
|
|
|
generate valid TLS credentials for itself. If secret information is passed in
|
|
|
|
the request (e.g. auth tokens supplied with the request or included in
|
|
|
|
ExtraInfo) then all communications from the node to the apiserver must take
|
|
|
|
place over a verified TLS connection.
|
|
|
|
|
|
|
|
Each node is additionally provisioned with the following information:
|
|
|
|
|
|
|
|
1. Location of the apiserver
|
|
|
|
2. Any CA certificates necessary to trust the apiserver's TLS certificate
|
|
|
|
3. Access tokens (if needed) to communicate with the CSR endpoint
|
|
|
|
|
|
|
|
These should not change often and are thus simple to include in a static
|
|
|
|
provisioning script.
|
|
|
|
|
|
|
|
## API Changes
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
### CertificateSigningRequest Object
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
We introduce a new API object to represent PKCS#10 certificate signing
|
|
|
|
requests. It will be accessible under:
|
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
`/apis/certificates/v1beta1/signingrequests/mycsr`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
It will have the following structure:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Describes a certificate signing request
|
|
|
|
type CertificateSigningRequest struct {
|
2016-04-12 19:27:11 +00:00
|
|
|
api.TypeMeta `json:",inline"`
|
|
|
|
api.ObjectMeta `json:"metadata,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// The certificate request itself and any additonal information.
|
|
|
|
Spec CertificateSigningRequestSpec `json:"spec,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// Derived information about the request.
|
|
|
|
Status CertificateSigningRequestStatus `json:"status,omitempty"`
|
|
|
|
|
|
|
|
// The current approval state of the request.
|
|
|
|
Approve CertificateSigningRequestApproval `json:"approve,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
}
|
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// This information is immutable after the request is created.
|
2016-02-01 21:41:40 +00:00
|
|
|
type CertificateSigningRequestSpec struct {
|
2016-04-14 21:03:31 +00:00
|
|
|
// base64-encoded PKCS#10 CSR data
|
2016-04-12 19:27:11 +00:00
|
|
|
CertificateRequest string `json:"request"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// Any extra information the node wishes to send with the request.
|
|
|
|
ExtraInfo []string `json:"extra,omitempty"`
|
|
|
|
}
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// This information is derived from the request by Kubernetes and cannot be
|
|
|
|
// modified by users. All information is optional since it might not be
|
|
|
|
// available in the underlying request. This is intented to aid approval
|
|
|
|
// decisions.
|
|
|
|
type CertificateSigningRequestStatus struct {
|
|
|
|
// Information about the requesting user (if relevant)
|
|
|
|
// See user.Info interface for details
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
UID string `json:"uid,omitempty"`
|
|
|
|
Groups []string `json:"groups,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// Fingerprint of the public key in request
|
|
|
|
Fingerprint string `json:"fingerprint,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// Subject fields from the request
|
|
|
|
Subject pkix.Name `json:"subject,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// DNS SANs from the request
|
|
|
|
Hostnames []string `json:"dns,omitempty"`
|
|
|
|
|
|
|
|
// IP SANs from the request
|
|
|
|
IPAddresses []string `json:"ip,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
}
|
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
type CertificateSigningRequestApproval struct {
|
|
|
|
// CSR approval state, one of Submitted, Approved, or Denied
|
|
|
|
State CertificateRequestState `json:"state"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// brief reason for the request state
|
|
|
|
Reason string `json:"reason,omitempty"`
|
|
|
|
// human readable message with details about the request state
|
|
|
|
Message string `json:"message,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
// If request was approved, the controller will place the issued certificate here.
|
|
|
|
Certificate []byte `json:"certificate,omitempty"`
|
2016-02-01 21:41:40 +00:00
|
|
|
}
|
2016-04-12 19:27:11 +00:00
|
|
|
|
|
|
|
type CertificateRequestState string
|
|
|
|
|
|
|
|
// These are the possible states for a certificate request.
|
|
|
|
const (
|
|
|
|
RequestSubmitted CertificateRequestState = "Submitted"
|
|
|
|
RequestApproved CertificateRequestState = "Approved"
|
|
|
|
RequestDenied CertificateRequestState = "Denied"
|
|
|
|
)
|
2016-02-01 21:41:40 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
We also introduce CertificateSigningRequestList to allow listing all the CSRs in the cluster:
|
|
|
|
|
|
|
|
```go
|
|
|
|
type CertificateSigningRequestList struct {
|
|
|
|
api.TypeMeta
|
|
|
|
api.ListMeta
|
|
|
|
|
|
|
|
Items []CertificateSigningRequest
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Certificate Request Process
|
|
|
|
|
|
|
|
### Node intialization
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
When the kubelet executes it checks a location on disk for TLS assets
|
|
|
|
(currently `/var/run/kubernetes/kubelet.{key,crt}` by default). If it finds
|
|
|
|
them, it proceeds. If there are no TLS assets, the kubelet generates a keypair
|
2016-04-12 19:27:11 +00:00
|
|
|
and self-signed certificate. We propose the following optional behavior:
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
1. Generate a keypair
|
|
|
|
2. Generate a CSR for that keypair with CN set to the hostname (or
|
|
|
|
`--hostname-override` value) and DNS/IP SANs supplied with whatever values
|
|
|
|
the host knows for itself.
|
|
|
|
3. Post the CSR to the CSR API endpoint.
|
|
|
|
4. Set a watch on the CSR object to be notified of approval or rejection.
|
|
|
|
|
|
|
|
### Controller response
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
The apiserver persists the CertificateSigningRequests and exposes the List of
|
|
|
|
all CSRs for an administrator to approve or reject.
|
|
|
|
|
|
|
|
A new certificate controller watches for certificate requests. It must first
|
|
|
|
validate the signature on each CSR and set `CertificateRequestState=Denied` on
|
|
|
|
any requests with invalid signatures. For valid requests, it will set
|
|
|
|
`CertificateRequestState=Submitted`. The controller will derive the information
|
|
|
|
in `CertificateSigningRequestStatus` and update that object. The controller
|
|
|
|
should watch for updates the approval state of any CertificateSigningRequest.
|
|
|
|
When a request is approved (signified by CertificateRequestState changing from
|
|
|
|
Submitted to Approved) the controller should generate and sign a certificate
|
|
|
|
based on that CSR, then update the approval subresource with the certificate
|
|
|
|
data.
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
### Manual CSR approval
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
An administrator using `kubectl` or another API client can query the
|
2016-04-12 19:27:11 +00:00
|
|
|
CertificateSigningRequestList and update the approval state of
|
|
|
|
CertificateSigningRequests. The default state is empty, indicating that there
|
|
|
|
has been no decision so far. Once a request has passed basic validation it will
|
|
|
|
be "Submitted". A state of "Approved" indicates that the admin has approved the
|
|
|
|
request and the certificate controller should issue the certificate. A state of
|
|
|
|
"Denied" indicates that the admin has denied the request. An admin may also
|
|
|
|
supply Reason and Message fields to explain the rejection.
|
|
|
|
|
|
|
|
## kube-apiserver support
|
|
|
|
|
|
|
|
The apiserver will present the new endpoints mentioned above and support the
|
|
|
|
relevant object types.
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
## kube-controller-manager support
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
To handle certificate issuance, the controller-manager will need access to CA
|
|
|
|
signing assets. This could be as simple as a private key and a config file or
|
|
|
|
as complex as a PKCS#11 client and supplementary policy system. For now, we
|
|
|
|
will add flags for a signing key, a certificate, and a basic policy file.
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
## kubectl support
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
To support manual CSR inspection and approval, we will add support for listing,
|
2016-04-12 19:27:11 +00:00
|
|
|
inspecting, and approving or denying CertificateSigningRequests to kubectl. The
|
|
|
|
interaction will be similar to
|
2016-02-01 21:41:40 +00:00
|
|
|
[salt-key](https://docs.saltstack.com/en/latest/ref/cli/salt-key.html).
|
|
|
|
|
|
|
|
Specifically, the admin will have the ability to retrieve the full list of
|
2016-04-12 19:27:11 +00:00
|
|
|
pending CSRs, inspect their contents, and set their states to one of:
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
1. **Approved** if the controller should issue the cert
|
|
|
|
2. **Denied** if the controller should not issue the cert
|
2016-02-01 21:41:40 +00:00
|
|
|
|
2016-04-12 19:27:11 +00:00
|
|
|
The suggested command for listing is `kubectl get csrs`. The approve/deny
|
|
|
|
interactions can be accomplished with normal updates, but would be more
|
|
|
|
conveniently accessed by direct subresource updates. We leave this for future
|
|
|
|
updates to kubectl.
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
## Security Considerations
|
|
|
|
|
|
|
|
### Endpoint Access Control
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
The ability to post CSRs to the signing endpoint should be controlled. As a
|
|
|
|
simple solution we propose that each node be provisioned with an auth token
|
|
|
|
(possibly static across the cluster) that is scoped via ABAC to only allow
|
|
|
|
access to the CSR endpoint.
|
|
|
|
|
|
|
|
### Expiration & Revocation
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
The node is responsible for monitoring its own certificate expiration date.
|
|
|
|
When the certificate is close to expiration, the kubelet should begin repeating
|
|
|
|
this flow until it successfully obtains a new certificate. If the expiring
|
2016-04-12 19:27:11 +00:00
|
|
|
certificate has not been revoked and the previous certificate request is still
|
|
|
|
approved, then it may do so using the same keypair unless the cluster policy
|
|
|
|
(see "Future Work") requires fresh keys.
|
2016-02-01 21:41:40 +00:00
|
|
|
|
|
|
|
Revocation is for the most part an unhandled problem in Go, requiring each
|
|
|
|
application to produce its own logic around a variety of parsing functions. For
|
|
|
|
now, our suggested best practice is to issue only short-lived certificates. In
|
|
|
|
the future it may make sense to add CRL support to the apiserver's client cert
|
|
|
|
auth.
|
|
|
|
|
|
|
|
## Future Work
|
2016-02-03 22:28:06 +00:00
|
|
|
|
2016-02-01 21:41:40 +00:00
|
|
|
- revocation UI in kubectl and CRL support at the apiserver
|
|
|
|
- supplemental policy (e.g. cluster CA only issues 30-day certs for hostnames *.k8s.example.com, each new cert must have fresh keys, ...)
|
|
|
|
- fully automated provisioning (using a handshake protocol or external list of authorized machines)
|
2016-02-03 22:28:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
|
|
|
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/proposals/kubelet-tls-bootstrap.md?pixel)]()
|
|
|
|
<!-- END MUNGE: GENERATED_ANALYTICS -->
|