-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Added service account external signer KEP #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| --- | ||
| title: Support external signing of service account keys | ||
| authors: | ||
| - "@micahhausler" | ||
| owning-sig: sig-auth | ||
| participating-sigs: [] | ||
| reviewers: | ||
| - "@mikedanese" | ||
| - "@liggit" | ||
| - "@tallclair" | ||
| approvers: | ||
| - "@mikedanese" | ||
| - "@liggit" | ||
| - "@tallclair" | ||
| editor: '@micahhausler' | ||
| creation-date: 2019-01-16 | ||
| last-updated: 2019-05-17 | ||
| status: implementable | ||
| see-also: [] | ||
| replaces: [] | ||
| superseded-by: [] | ||
| --- | ||
|
|
||
| # Support external signing of service account keys | ||
|
|
||
| ## Table of Contents | ||
|
|
||
| <!-- toc --> | ||
| - [Summary](#summary) | ||
| - [Motivation](#motivation) | ||
| - [Goals](#goals) | ||
| - [Non-Goals](#non-goals) | ||
| - [Proposal](#proposal) | ||
| - [Preserve existing behavior](#preserve-existing-behavior) | ||
| - [Updates to API server token generation](#updates-to-api-server-token-generation) | ||
| - [New API](#new-api) | ||
| - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) | ||
| - [Risks and Mitigations](#risks-and-mitigations) | ||
| - [Graduation Criteria](#graduation-criteria) | ||
| - [Implementation History](#implementation-history) | ||
| <!-- /toc --> | ||
|
|
||
| ## Summary | ||
|
|
||
| The Kubernetes API server has always read service account keys from disk as the process starts, and kept them in memory for the duration of the server's lifetime. As the API server can now verify and issue projected volume tokens, it would be advantageous to support external signing and verifying of token data over an API, as well as reading public keys from an API. | ||
|
|
||
| ## Motivation | ||
|
|
||
| For operators who want to regularly rotate the signing and verifying keys for projected volume tokens, the Kubernetes API server must be restarted in order to use a new key. To facilitate easy key rotation, this KEP includes an proposal for a grpc API to support out of process signing and listing of signing keys. | ||
|
|
||
| ### Goals | ||
|
|
||
| - Support for out-of-process JWT signing | ||
| - Support for listing public verifying keys | ||
| - Preserve existing behavior and performance for keys not read over a socket | ||
|
|
||
| ### Non-Goals | ||
|
|
||
| - Reading TLS serving certificates and key from a socket or reloading of the API server with new cert and key | ||
| - Reading any other certificates from a file | ||
|
|
||
| ## Proposal | ||
|
|
||
| ### Preserve existing behavior | ||
|
|
||
| The API server flags `--service-account-key-file` and `--service-account-signing-key-file` will continue be used for reading from files. | ||
|
|
||
| ### Updates to API server token generation | ||
|
|
||
| As of Kubernetes v1.13.2, the API server uses the functions `JWTTokenGenerator` and `JWTTokenAuthenticator`. New types that implement the `TokenGenerator` interface and support token validation will be added to `k8s.io/kubernetes/pkg/serviceaccount/`. | ||
|
|
||
| ### New API | ||
|
|
||
| I'm proposing creating a new versioned grpc API under `k8s.io/kubernetes/pkg/serviceaccount`. This will be similar to how the KMS envelope encryption has an API at `k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/v1beta1/service.proto` | ||
|
|
||
| ```proto | ||
| syntax = "proto3"; | ||
|
|
||
| package v1alpha1; | ||
|
|
||
| service KeyService { | ||
| // Sign an incoming payload | ||
| rpc SignPayload(SignPayloadRequest) returns (SignPayloadResponse) {} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What validation does the API server do on the response? Can the signer add additional claims to the token? Does it have to be a JWT? I'd prefer some guarantees here for the sake of portability.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically the signer could, as the API server is just sending bytes over, but this is just modeled after |
||
| // List all active public keys | ||
| rpc ListPublicKeys(ListPublicKeysRequest) returns (ListPublicKeysResponse) {} | ||
| } | ||
|
|
||
| message SignPayloadRequest { | ||
| // payload is the content to be signed. JWT headers must be included by the caller | ||
| bytes payload = 1; | ||
micahhausler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // algorithm specifies which algorithm to sign with | ||
| string algorithm = 2; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is useful. The apiserver reads this from the ListPublicKeys response than passes it back to the signer? What happens if it gets it wrong e.g. if a active key is rotated and the new key has a different algorithm?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that we are somewhat constrained by the OpaqueSigner interface.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with the algorithm comment, it should be a list. A list would allow to specify algorithms and you are able to migrate, in case one algorithm is broken.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are limited in what we can do here because We need something like an // OpaqueSigner is an interface that supports signing payloads with opaque
// private key(s). Private key operations preformed by implementors may, for
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
// transparently to the user of this interface.
type OpaqueSigner interface {
// Public returns the public key of the current signing key.
Public() *JSONWebKey
// Algs returns a list of supported signing algorithms.
Algs() []SignatureAlgorithm
// SignPayload signs a payload with the current signing key using the given
// algorithm.
SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error)
}
// SignatureAlgorithm represents a signature (or MAC) algorithm.
type SignatureAlgorithm string
// JSONWebKey represents a public or private key in JWK format.
type JSONWebKey struct {
Key interface{}
Certificates []*x509.Certificate
KeyID string
Algorithm string
Use string
}
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hah I spoke too soon: // OpaqueVerifier is an interface that supports verifying payloads with opaque
// public key(s). An OpaqueSigner may rotate signing keys transparently to the
// user of this interface.
type OpaqueVerifier interface {
VerifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
}I believe we can have
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me |
||
| } | ||
| message SignPayloadResponse { | ||
| // content returns the signed payload | ||
| bytes content = 1; | ||
| } | ||
|
|
||
|
|
||
| message PublicKey { | ||
| // public_key is a PEM encoded public key | ||
| bytes public_key = 1; | ||
| // certificate is a concatenated list of PEM encoded x509 certificates | ||
| bytes certificates = 2; | ||
| // key_id is the key's ID | ||
| string key_id = 3; | ||
| // algorithm states the algorithm the key uses | ||
| string algorithm = 4; | ||
| } | ||
|
|
||
| message ListPublicKeysRequest {} | ||
| message ListPublicKeysResponse { | ||
| // key_id is the key's ID | ||
| string active_key_id = 1; | ||
| // public_keys is a list of public verifying keys | ||
| repeated PublicKey public_keys = 2; | ||
| } | ||
| ``` | ||
|
|
||
| ### Implementation Details/Notes/Constraints | ||
|
|
||
| The API server flag `--service-account-key-file` can be specified multiple times for legacy SA tokens and projected tokens. Validation keys from this flag will be merged with the response of `ListPublicKeys()`. A new flag `--key-service-url` will be added to the API server specifying a unix socket where the key service will be accessible. | ||
|
|
||
| ### Risks and Mitigations | ||
|
|
||
| New token generation and validation could suffer a performance difference when reading over a socket, as an external process will be signing data. | ||
|
|
||
| Signing and verifying tokens over a grpc API carries the risk of a server side request forgery, where a malicious client could generate tokens. To mitigate this risk, the API will only be accessible over a unix socket. | ||
|
|
||
| ## Graduation Criteria | ||
|
|
||
| <!-- TODO --> | ||
|
|
||
| ## Implementation History | ||
|
|
||
| * Initial PR: kubernetes/kubernetes#73110 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a
tin@liggitt