-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Update bootstrap design doc with kubeadm UX #381
Conversation
* `--discovery-token` If set, this is a token that is created in the new cluster that is used for signing discovery bootstrap. If not set, it defaults to the value from `--token`. If set to the empty string, there is no token created. | ||
* `--discovery-token-ttl` Similar TTL for token. | ||
* `--bootstrap-auth-token` (not strictly part of this proposal) If set, this token is set into the cluster to be used for doing authentication for TLS bootstrap. If set to the empty string there is no token created. | ||
* `--boostrap-auth-token-ttl` |
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.
Add description (only flag missing one).
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.
Added more semantics here.
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.
Please be super-specific on the .
separator here!!
Also mention kubernetes/client-go#114
And include what we have to do with RBAC: kubernetes/kubeadm#169
And that the system:bootstrappers
Group should be granted access to the Certs API
@@ -79,7 +79,6 @@ This is really a shorthand for someone doing something like (assuming we support | |||
curl https://example.com/mycluster.json | kubeadm join --cluster-info-file=- |
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.
I don't think we'll have time to impl this
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.
in v1.6
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.
This feature could be added without breaking compatibility
### `kubeadm init` flags | ||
|
||
* `--token` If set, this injects the bootstrap token to use when initializing the cluster. If this is unset, then a random token is created and shown to the user. If set explicitly to the empty string then no token is generated or created. | ||
* `--token-ttl` If set, this sets the TTL for the lifetime of this token. Defaults to 0 which means "forever" |
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.
I actually don't think we have to support this, do you?
If the user wants a TTL, they can remove the token automatically and add a new one that suits their needs.
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.
Don't want to get too heavy on the flags here
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.
2 reasons for this:
- The "never expire" default is a bit on the sketchy side from a security point of view. Having an option from the start is a small way to start mitigating it.
- Even having this parameter there with a help string that says "forever" will clue people in to what the default behavior is. This is a cheap way to document something that is somewhat security sensitive.
* `--token-ttl` If set, this sets the TTL for the lifetime of this token. Defaults to 0 which means "forever" | ||
* `--discovery-token` If set, this is a token that is created in the new cluster that is used for signing discovery bootstrap. If not set, it defaults to the value from `--token`. If set to the empty string, there is no token created. | ||
* `--discovery-token-ttl` Similar TTL for token. | ||
* `--bootstrap-auth-token` (not strictly part of this proposal) If set, this token is set into the cluster to be used for doing authentication for TLS bootstrap. If set to the empty string there is no token created. |
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.
This doesn't make sense I think. We should just do the --token
on the init side, I think this bloats the options here, since everything here's configurable with kubeadm token
afterwards
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.
--bootstrap-auth-token
should be --tls-bootstrap-auth-token
instead on kubeadm join
and will work if there's a such secret with usage-bootstrap-authentication
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.
So -- do we want to support having a different token for different usages? I can see going down to a single token but I want to make sure we don't preclude scenarios here.
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.
I'll remove the specialized flags. We can look to add them back later.
* `--discovery-url` If set this will grab the cluster-info data (a kubeconfig) from a URL. This will support `http` and `https+insecure` but it we will warn if this is not `https`. This also supports `username:password@host` syntax for doing HTTP auth. | ||
* `--discovery-file` If set, this will load the cluster-info from a file. | ||
* `--discovery-token` If set, (or set via `--token`) then we will be using the token scheme described above. | ||
* `--discovery-token-url` This is the URL to hit to get the ConfigMap that contains the kubeconfig and signatures. This defaults to `https+insecure://<api-server>/api/v1/namespaces/kube-public/configmaps/cluster-info`. |
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.
I don't think this is needed. I've implemented this as a client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ClusterInfoConfigMap)
and I don't think this path should be configurable.
If the user needs to config this, go with https or file
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.
I'm OK removing this for now. The only situation I can think of, at some point, may involve federation type stuff where I use one cluster to discover another cluster. But I'm having a hard time really wrapping my head around those scenarios.
|
||
* `kubeadm token create` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. | ||
* `--token` The actual token value (in `id.secret` form) to write in. If unset, a random value is generated. | ||
* `--usages` A list of usages. Defaults to `signing,authentication` |
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.
Point out the signing
makes is valid for --discovery-token
and authentication
makes it valid for --tls-bootstrap-token
* `kubeadm token create` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. | ||
* `--token` The actual token value (in `id.secret` form) to write in. If unset, a random value is generated. | ||
* `--usages` A list of usages. Defaults to `signing,authentication` | ||
* `--ttl` The TTL for this token. |
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.
this is a duration, point that out
* `kubeadm token delete <token-id>|<token-id>.<token-secret>` | ||
* Users can either just specify the id or the secret. This will delete the token if it exists. | ||
* `kubeadm token list` | ||
* List tokens in a table form listing out the `token-id.token-secrert`, the TTL, the absolute expiration time and the usages. |
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.
nit: token-id.token-secret
So you mean this should be like this with both the full expiration time and the time that's left from Now()
?
TOKEN TTL EXPIRATION USAGES
abcdef.0123456789abcdef 8h 01.01.2018T00:00:00.000 signing,authentication
or just
TOKEN TTL USAGES
abcdef.0123456789abcdef 8h signing,authentication
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.
I was thinking listing the expiration time in addition to the TTL. 2 reasons -- TTL will be rounded to be human readable and it doesn't hold up if someone is looking at the output from a script.
But up to you.
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.
lgtm, small spelling error.
``` | ||
|
||
If the user requires some auth to the HTTPS server (to keep the ClusterInfo object private) that can be done in the curl command equivalent. Or we could eventually add it to `kubeadm` directly. | ||
**Note:** support for leoading from stdin for `--discovery-file` may not be implemented immediately. |
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.
spelling (leoading)
### `kubeadm join` flags | ||
|
||
* `--token` This sets the token for both discovery and bootstrap auth. | ||
* `--discovery-url` If set this will grab the cluster-info data (a kubeconfig) from a URL. This will support `http` and `https+insecure` but it we will warn if this is not `https`. This also supports `username:password@host` syntax for doing HTTP auth. |
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.
I'm of the opinion that this should not support http or or https+insecure. It doesn't today.
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.
Sure -- happy to remove it. We can always add it back later if we see a compelling case.
@mikedanese @dmmcquay -- PTAL |
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.
Great progress made here!
Some things yet to fix though...
|
||
### `kubeadm init` flags | ||
|
||
* `--token` If set, this injects the bootstrap token to use when initializing the cluster. If this is unset, then a random token is created and shown to the user. If set explicitly to the empty string then no token is generated or created. This token is used for both discovery and TLS bootstrap. |
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.
This token is used for both discovery and TLS bootstrap.
suggest: This token has both the usage-bootstrap-signing
and usage-bootstrap-authentication
flags set to true, which means it will be used for both discovery and TLS bootstrap.
### `kubeadm join` flags | ||
|
||
* `--token` This sets the token for both discovery and bootstrap auth. | ||
* `--discovery-url` If set this will grab the cluster-info data (a kubeconfig) from a URL. Due to the sensitive nature of this data, we will only support https URLs. This also supports `username:password@host` syntax for doing HTTP auth. |
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.
Hmm, can we rename these to --tls-bootstrap-from-url
or --tls-bootstrap-url
instead?
I think that makes more sense since what's actually happening behind the scenes is just the TLS Bootstrap, nothing else...
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.
This is discovery, not tls bootstrap. Discussion below.
|
||
* `--token` This sets the token for both discovery and bootstrap auth. | ||
* `--discovery-url` If set this will grab the cluster-info data (a kubeconfig) from a URL. Due to the sensitive nature of this data, we will only support https URLs. This also supports `username:password@host` syntax for doing HTTP auth. | ||
* `--discovery-file` If set, this will load the cluster-info from a file. |
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.
Same goes for this arg: can it be --tls-bootstrap-from-file
?
* `--token` This sets the token for both discovery and bootstrap auth. | ||
* `--discovery-url` If set this will grab the cluster-info data (a kubeconfig) from a URL. Due to the sensitive nature of this data, we will only support https URLs. This also supports `username:password@host` syntax for doing HTTP auth. | ||
* `--discovery-file` If set, this will load the cluster-info from a file. | ||
* `--discovery-token` If set, (or set via `--token`) then we will be using the token scheme described above. |
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.
Sorry if I was unclear last review (I was in a hurry), but I do think we should keep what you had in terms of exposing both --discovery-token
and --tls-bootstrap-token
and using --token
as a shorthand for setting both.
This way, the only --discovery-xxx
flag we have is token, because that's actually the only one that's gonna do discovery with the cluster-info ConfigMap, JWS validation and all that. I see this as two phases:
- Unsecure HTTP GET call to the cluster-info ConfigMap for getting the CA and master list and validate the content we recieved using the token -- this is discovery and is only available for the token method
- TLS Bootstrap -- do the CSR dance and connect to the API Server with whatever creds you have (existing client cert or token), but this is most often a token regardless of method -- this phase is available for file, url and token.
I propose to define this in the doc and refer to 1. as discovery
and 2. as just TLS bootstrap.
In the future, 2. may also be passed to the kubelet: kubernetes/kubeadm#173
So TL;DR; --discovery-token
is used in 1. and --tls-bootstrap-token
is used in 2. and --token
sets both
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.
I'm confused here.
Discovery == "getting and trusting the cluster-info file". We have 3 ways to do this: token, HTTPS endpoint (trusting the system CA bundle) and local file. This is driven by the --discovery-*
flags.
I think you are wrong wrt 2. The TLS bootstrap dance only works with a token. It is what is used to authenticate to the API server using temporary credentials (the token) in order to submit a CSR. That would be the --tls-bootstrap-token
flag. I'll make sure we have that.
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.
Ok, I see how you're thinking.
Let's see if we can come to a conclusion here... This is what I thought when I wrote that comment:
Discovery
Scenario A: We only have a token and an address to an API Server. What do we do?
We still need a CA cert, and in the future the addresses to the rest of the API Servers in the clusters.
We construct a KubeConfig file like this:
apiVersion: v1
kind: Config
clusters:
- cluster:
server: https://BOOTSTRAP_SERVER:PORT
insecureTLSVerify: false
name: discovery
contexts:
- context:
cluster: discovery
user: ""
name: discovery
current-context: discovery
and do
client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get("cluster-info")
in order to get the KubeConfig with the CA cert and cluster addresses + the JWS token.
Then we validate the JWS token against our own token and the KubeConfig file we've got.
If everything succeeds, we'll end up with a KubeConfig file like this:
apiVersion: v1
kind: Config
clusters:
- cluster:
server: https://SERVER:PORT{https://SERVER2:PORT2,...}
certificate-authority-data: <cacertbytes>
name: tlsbootstrap-ready
users:
- name: tlsbootstrap-ready
user:
token: <tls-bootstrap-token-id>:<tls-bootstrap-token-secret>
contexts:
- context:
cluster: tlsbootstrap-ready
user: tlsbootstrap-ready
name: tlsbootstrap-ready
current-context: tlsbootstrap-ready
This is what we need for the next phase (TLS Bootstrap). I.e. this is the output of the discovery phase and the input to the TLS bootstrap phase in my world.
Scenario B: We only have a KubeConfig file with credentials that we can use to talk in a secure manner to the cluster. What do we do?
Well, since we have all the information needed for TLS Bootstrap already, we'll just skip this phase and go on to the next.
At least, this is what we do now in kubeadm. I don't know, maybe it makes sense to hit the cluster-info ConfigMap regardless in order to check for updated information (which can be more than the CA and the apiserver list in the future). If that was what you thought, then I support that. But FYI, that's far from what's implemented now.
TLS Bootstrap
This is just "the normal" TLS bootstrap flow, which in the future may/should be implemented by the kubelet itself for easier lifecycle management of those certs.
The only thing we need as an input here is a KubeConfig file that has:
- at least one address to an apiserver
- a CA cert so we can trust the apiserver in question
- an user that has access to the Certificates API. Using a token here is by far the most common method, but I don't think it matters how you're connecting. By default in kubeadm, the
system:bootstrappers
group is granted access to the Certs API. If the cluster admin decided to make client certs that hassystem:bootstrapper
as its Organization, I guess this will just work as well as using a token.
If we should hit the cluster-info endpoint regardless of (token|file|https)
, then there should be --discovery-(file|token|https)
. If we should just do a "fast-forward" and use the kubeconfig taken from the file or https and give it to TLS bootstrap directly (how it's implemented currently), this should be --tls-bootstrap-(https|file)
Anyway I think it's very important to define the inputs/outputs here. WDYT about this?
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.
Wrt Discovery -- This is all outlined in the first section of the document. This is about distributing information about the cluster itself including the CA bundle for that server. Over time we can use this to find a set of API servers but that is for the future. This has nothing to do with auth at all and it is bad news if you put TLS client certificates in cluster-info. Earlier in the doc I say "It is expected that the kubeconfig
file will have a single unnamed Cluster
entry. Other information (especially authentication secrets) must be omitted."
But, once you find a cluster, how do you get a unique client certificate signed by a CA that the server trusts? That is the TLS bootstrap flow. And the option we are using here os to also use the token for that. If you have a client certificate that you've already gotten via other means we should support that. But that really isn't "bootstrap" in any way.
The client TLS stuff is a bit out of scope for this doc but I'd be cool with a set of flags like this:
--client-tls-bootstrap-token
Configure client TLS by using a token and the Cert API--client-tls-certificate
and--client-tls-key
Instead of doing TLS bootstrapping with a token, read the client certificate and key from these files. Install them in the appropriate directories. Verify that they can be used to talk to the server. Note that the flag names mirror thekubectl
flags somewhat.
If either --client-tls-certificate
or --client-tls-key
are set then it is assumed that users do not want to use the cert API to bootstrap TLS. If both are not set or they cannot successfully be used to communicate with the server then an error is thrown.
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.
This has nothing to do with auth at all and it is bad news if you put TLS client certificates in cluster-info
Sorry, that was not what I meant. I meant that you could make a TLS-Bootstrap-only client cert (instead of a token) in your KubeConfig file that will be used for TLS bootstrapping i.e. in the second phase of what kubeadm does. I just said that I think this is possible as an answer to:
I think you are wrong wrt 2. The TLS bootstrap dance only works with a token.
--client-tls-certificate and --client-tls-key Instead of doing TLS bootstrapping with a token, read the client certificate and key from these files. Install them in the appropriate directories. Verify that they can be used to talk to the server. Note that the flag names mirror the kubectl flags somewhat
I agree those flags would be really cool.
`kubeadm` provides a set of utilities for manipulating token secrets in a running server. | ||
|
||
* `kubeadm token create` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. | ||
* `--token` The actual token value (in `id.secret` form) to write in. If unset, a random value is generated. |
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.
I think kubeadm token create [token] [flags]
is much more UX-friendly. If [token]
isn't specified, it will be autogenerated. Sounds good?
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.
sure
* `kubeadm token create` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. | ||
* `--token` The actual token value (in `id.secret` form) to write in. If unset, a random value is generated. | ||
* `--usages` A list of usages. Defaults to `signing,authentication`. Signing is used to sign the config-map used for discovery. Authentication is used to authenticate for TLS bootstrap. | ||
* `--ttl` The TTL for this token. This sets the expiration of the token as a duration from the current time. |
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.
Everything here is UTC time I suppose?
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.
Since TTL is "time from now" UTC doesn't come in to play. This will be converted into an absolute UTC time as it is written in to the token secret.
* `--usages` A list of usages. Defaults to `signing,authentication`. Signing is used to sign the config-map used for discovery. Authentication is used to authenticate for TLS bootstrap. | ||
* `--ttl` The TTL for this token. This sets the expiration of the token as a duration from the current time. | ||
* `kubeadm token delete <token-id>|<token-id>.<token-secret>` | ||
* Users can either just specify the id or the secret. This will delete the token if it exists. |
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.
specify the id or the secret
specify the token id or the full token
|
||
See https://github.com/kubernetes/client-go/issues/114 for details on creating a shared package with common constants for this scheme. | ||
|
||
This proposal assumes RBAC to lock things down in a couple of ways. First, it will open up `kube-public` so that it is readable by unauthenticated users. Next, it will make it so that the identities in the `system:bootstrappers` group can only be used to submit certs API to submit CSRs. After a TLS certificate is created, that identity should be used instead of the bootstrap token. |
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.
Initially, we're only opening get configmap
in kube-public
to avoid getting opening ourselves to the world too much
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.
There is nothing else in kube-public
. The whole idea is that it will be totally public. But, sure.
@@ -139,11 +139,10 @@ The following keys are on the secret data: | |||
* **token-secret**. As defined above. | |||
* **expiration**. After this time the token should be automatically deleted. This is encoded as an absolute UTC time using RFC3339. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If omitted or some other string, it defaults to `false`. |
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.
In kubeadm, this defaults to true
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.
This is for secret itself, not the UX for kubeadm. If a usage is missing from the token secret, users should assume that usage isn't allowed. I'll make it more clear in the text.
@@ -139,11 +139,10 @@ The following keys are on the secret data: | |||
* **token-secret**. As defined above. | |||
* **expiration**. After this time the token should be automatically deleted. This is encoded as an absolute UTC time using RFC3339. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If omitted or some other string, it defaults to `false`. | |||
* **usage-bootstrap-authentication**. Set to true to indicate that this token should be used for authenticating to the API server. This will auth as a user that is `system:bootstrap:<token-id>` in the group `system:bootstrappers`. |
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.
Defaults to true in kubeadm
We should also point out that access to a bootstrap token equals root access to the cluster if I'm planning to propose a solution to that problem (probably a new API Object to the Certs API Group if other things doesn't work, sigh) |
Pretty sure that's "working as designed" until we partition node permissions.
To accomplish what, dynamically enabling/disabling that setting? I have on my list to replace the hard coded "always approve" group with a permission check in the controller manager using a subject access review ("did the user that submitted this CSR for a kubelet client cert have permission to 'join' 'nodes' named 'mynode'", etc) |
Added a comment saying that our documentation should make it clear how sensitive this token is. |
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.
Ok, so now I see what confused me a little:
- The current behaviour of kubeadm at HEAD -- totally different than described here
- The fact the content in the file/https kubeconfig is sufficent for doing the TLS Bootstrap straight away. I thought we could just go on and do that. However, it is preferable to hit the cluster-info ConfigMap in any case, I agree with this. Maybe you could clearly point out that when using the out-of-band (file) or https method, the implementation should still talk with the cluster-info endpoint?
However, I think that it should be stated that when using https/file, the implementation should (must?) omit the user info when getting the information from the cluster-info
ConfigMap in order to not unnecessarily send auth data when it's not necessary (this is public info after all).
Also, in this case of https/file, the communication between the implementation (kubeadm) and the API Server MUST BE secure with https
.
@@ -70,16 +70,16 @@ Note that TLS bootstrap (which establishes a way for a client to authenticate it | |||
If the ClusterInfo information is hosted in a trusted place via HTTPS you can just request it that way. This will use the root certificates that are installed on the system. It may or may not be appropriate based on the user's constraints. |
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.
You haven't defined ClusterInfo. I think you just mean the KubeConfig file with the list of addresses to API Servers with the cluster-info
ConfigMap exposed and the CA cert(s)
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.
It is defined in the section called "Cluster location information" but isn't as clear as it could be. I'll add more text there and call out the term.
@liggitt Indeed "by design", but I don't think our users realize this really At a first glance, would a policy like this make sense to you? kind: CSRApproverBinding
apiVersion: certificates.k8s.io/v1beta2
metadata:
name: my-binding-between-generic-tokens-and-kubelet-privs
subject: // The one that posted the CSR
kind: Group // Can also be User or ServiceAccount
name: system:bootstrappers
allowedAttributes: // Allowed attributes that the CSR can include for the controller-manager to auto-approve
groups:
- system:nodes
- system:basic-user
users:
- some-misc-user
- some-other-allowed-user-to-claim
// any other attributes needed here? or it could be kind: CSRApproverBinding
apiVersion: certificates.k8s.io/v1beta2
metadata:
name: my-approval-of-new-master-privs-with-token-abcdef
subject: // The one that posted the CSR
kind: User
name: system:bootstrapper:abcdef
allowedAttributes: // Allowed attributes that the CSR can include for the controller-manager to auto-approve
groups:
- system:ḿasters
users:
- kube-apiserver This way we somehow could limit the maximum damage that can be made if a token is exploited. This was just the first thing I came to think about... feel free to shoot it down :) Anyway, I'd want a mapping between a Group/User/SA and the maximum allowed attributes it should be able to request for the controller-manager to auto-approve. This way we could make different kinds of tokens that can ask for different kinds of privs. We also achieve a pretty good balance between security and convenience (we don't have to approve the CSRs by hand). WDYT? (Sorry, I know this isn't exactly what's discussed in the PR, but it seems quite related and since we started talking about this already...) |
Pushed some more changes. PTAL. Hopefully this clears up some of the confusion. I'm really trying to keep bootstrapping the server ID separate from TLS bootstrap. While we want to use the same token for both for ease of use, they are really pretty separate. |
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.
👍 it gets clearer and clearer.
The last thing I'd like here is an explanation why we should copy the ClusterInfo kubeconfig out-of-band to a node just to fetch the ClusterInfo object once again (via HTTPS).
Something like:
When using the HTTPS or File method, the implementation should securely (via HTTPS) contact the API Server(s) that exposes the public ClusterInfo object once again in order to refresh the credentials. Otherwise, the out-of-band ClusterInfo object could get out of sync with the latest information stored in the ConfigMap. Imagine this situation:
Information in the ClusterInfo ConfigMap in API Server named apiserver1
... the rest of the kubeconfig object...
cluster:
servers: apiserver1:6443
certificate-authorithy-data: cacert1
Then, out-of-band, this information is transferred to a node in file my-discovery.yaml
.
One day after, the publicly-available ClusterInfo kubeconfig is changed to:
... the rest of the kubeconfig object...
cluster:
servers: apiserver1:6443,apiserver2:6443
certificate-authorithy-data: cacert1,cacert2
and if the implementation (e.g. kubeadm) didn't refresh this information by connecting with my-discovery.yaml
and then updating its cached data, the node would never know that a new apiserver and ca cert have been added to the cluster.
Something like that, just to make it clear that the client MUST refresh the data also when using the HTTPS/File method, because the information might be stale.
|
||
Another controller (`tokencleaner`) is introduced that deletes tokens that are past their expiration time. | ||
|
||
Logically these controllers could run as a component in the control plane. But, for the sake of efficiency, they are bundeled as part of the Kubernetes Control Manager. |
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.
controller-manager
Right now the ClusterInfo is pretty much just a single address and the CA bundle. There is no real way to rotate certificates without notifying everyone out of band. I think it is okay for kubeadm to update stuff from the cluster-info configmap but I don't think it is a MUST. But it MUST verify the certificate chain when hitting the server. So, the only time insecure HTTP should be used is when we have the token to verify the contents of the payload. |
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.
I think it is okay for kubeadm to update stuff from the cluster-info configmap but I don't think it is a MUST
Fair enough. With HA and kubeconfig v2 this becomes a MUST though, but I'm fine with defining it generally as a MAY.
+100 to adding the section that describes that the implementation MUST verify that the CA cert for the given server is valid, I was missing that from the original proposal.
This is fine now I think, we should probably mention how to hook up this design with the next generation of KubeConfig v2 (i.e. how do we do the upgrade, what will the constants for be, etc.), but that can wait.
Please squash though 😉 |
Signed-off-by: Joe Beda <joe.github@bedafamily.com>
7ea3e50
to
6a96640
Compare
OK! Squashed. I think that we are in good shape but I'd like you to take one more pass before it gets merged. I'd also like to give @mikedanese a chance to give it another look if he wants. |
Signed-off-by: Joe Beda <joe.github@bedafamily.com>
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.
One legit idea re description
and one crazy one with cluster-role...
I'd also want this spec to state how to deal with duplicate tokens. What happens if:
- two tokens have the same id, but different content (secrets for instance) -- this one is nasty
- two tokens have exactly the same content?
@@ -138,12 +144,11 @@ The following keys are on the secret data: | |||
* **token-id**. As defined above. | |||
* **token-secret**. As defined above. | |||
* **expiration**. After this time the token should be automatically deleted. This is encoded as an absolute UTC time using RFC3339. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If omitted or some other string, it defaults to `false`. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If this is missing from the token secret or set to any other value, the usage is not allowed. | |||
* **usage-bootstrap-authentication**. Set to true to indicate that this token should be used for authenticating to the API server. If this is missing from the token secret or set to any other value, the usage is not allowed. The bootstrap token authenticagtor will use this token to auth as a user that is `system:bootstrap:<token-id>` in the group `system:bootstrappers`. | |||
|
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.
Hmm, I wonder whether we actually should add a well-known description
field here?
Don't wanna bloat the spec, but as we grow the responsibilities of these tokens; we might for example want to allocate a token for the scheduler and the controller-manager (kubernetes/kubeadm#172) respectively, and then it would probably be helpful with a human-friendly message that states "this token is for the scheduler" and if the user goes and removes it, the scheduler will lose access to the apiserver.
In any case, when you have 15 different tokens with different TTLs and usages and a new person comes and looks at it, he/she will have no idea where they are used and what they are used for. Adding a generic description
field to this spec and to kubeadm token list
will clarify things a lot. It also aligns with the rest of k8s where the metadata
field is constantly present. WDYT?
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.
I'm in favor of generic free form description fields. I'm happy to add it. It is a bit of an overload -- this could go in an annotation or it could go in the of the secret.
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.
I just think it would be really helpful for kubeadm token list
to have a DESCRIPTION
column.
I'm open to using a metadata field on the Secret as well, but it feels like we're better of being consistent with the rest of the data
|
||
`kubeadm` provides a set of utilities for manipulating token secrets in a running server. | ||
|
||
* `kubeadm token create [token]` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. |
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.
I don't want to bloat RBAC into this spec either, but would it make sense to add a --cluster-role
to this command?
This could essentially be a shorthand for
kubectl create clusterrolebinding bootstrap-token-(token-id) --cluster-role (cluster-role) --user system:bootstrap:(token-id)
that would be applied in the same go if this arg is specified. This way the user could easily attach an extra role...
Well, feel free to ignore this, it was just a crazy idea that sprung to mind :)
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.
I don't think we want to encourage people to use these tokens outside of just bootstrapping. We can always add it later if we find another bootstrap-ish use for it, but, for now, I'd like to keep these scoped.
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.
I'm ok with that
Wrt duplicate tokesn -- in the bootstrap signer I just ignore one of them and warn. See https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/bootstrap/bootstrapsigner.go#L280. I'm also happy to say something like "if two secrets have the same token ID then that is considered a corrupt configuration and all of the secrets with that ID are ignored". That might be safer and more reliable but will require changes to the bootstrap signer. |
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.
I'm in favor of marking a duplicated token invalid by all consumers. Please add that to the doc
* `kubeadm token delete <token-id>|<token-id>.<token-secret>` | ||
* Users can either just specify the id or the full token. This will delete the token if it exists. | ||
* `kubeadm token list` | ||
* List tokens in a table form listing out the `token-id.token-secret`, the TTL, the absolute expiration time and the usages. |
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.
I think it might make sense to output the Secret name in the first column here actually.
Otherwise you'll have a very hard time knowing which token matches which Secret without iterating all of them
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.
I'd like to avoid that. Most consumers will be unaware that the tokens are implemented as kube-system secrets under the covers. It will be confusing and break the mental model for users at this point.
Instead, we could warn loudly if we have duplicates and list all the secret names. Perhaps with instructions on how to use kubectl
to delete them all and recreate the secret.
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.
It will be confusing and break the mental model for users at this point.
Fair point
|
||
`kubeadm` provides a set of utilities for manipulating token secrets in a running server. | ||
|
||
* `kubeadm token create [token]` Creates a token server side. With no options this'll create a token that is used for discovery and TLS bootstrap. |
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.
I'm ok with that
@@ -138,12 +144,11 @@ The following keys are on the secret data: | |||
* **token-id**. As defined above. | |||
* **token-secret**. As defined above. | |||
* **expiration**. After this time the token should be automatically deleted. This is encoded as an absolute UTC time using RFC3339. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If omitted or some other string, it defaults to `false`. | |||
* **usage-bootstrap-signing**. Set to `true` to indicate this token should be used for signing bootstrap configs. If this is missing from the token secret or set to any other value, the usage is not allowed. | |||
* **usage-bootstrap-authentication**. Set to true to indicate that this token should be used for authenticating to the API server. If this is missing from the token secret or set to any other value, the usage is not allowed. The bootstrap token authenticagtor will use this token to auth as a user that is `system:bootstrap:<token-id>` in the group `system:bootstrappers`. | |||
|
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.
I just think it would be really helpful for kubeadm token list
to have a DESCRIPTION
column.
I'm open to using a metadata field on the Secret as well, but it feels like we're better of being consistent with the rest of the data
A third option for duplicate tokens -- make the secret name a MUST and eliminate the possibility. Warn of all secrets with the bootstrap token type that don't match up. |
IMO, this is the best alternative. We'll restrict bootstrap tokens to be named Then the listing becomes so much easier, you can just rely on (In any case, |
Ok -- I'll write this. But I'm not in favor of the TokenCleaner removing those corrupt tokens. This may lead to the case where someone writes something to the cluster and before they can debug it just goes away. We can always add that back in later. Ok -- I'll update the spec and start a delta on the BootstrapSigner. |
Yup, just a suggestion. But I'd at least like the TokenCleaner to annotate or label it "invalid" in some way. |
Also adds a free form description field to the token secret keys. Signed-off-by: Joe Beda <joe.github@bedafamily.com>
PTAL -- I'm going to hold off on having the TokenCleaner annotate the secret as invalid at this point. This is a new write path in that secret and the chances of this happening are pretty slim -- someone has to be managing secrets outside of |
Signed-off-by: Joe Beda <joe.github@bedafamily.com>
This aligns with spec changes coming in kubernetes/community#381. Signed-off-by: Joe Beda <joe.github@bedafamily.com>
This aligns with spec changes coming in kubernetes/community#381. Signed-off-by: Joe Beda <joe.github@bedafamily.com>
@@ -130,6 +134,8 @@ The first part of the token is the `token-id`. The second part is the `token-se | |||
|
|||
This new type of token is different from the current CSV token authenticator that is currently part of Kubernetes. The CSV token authenticator requires an update on disk and a restart of the API server to update/delete tokens. As we prove out this token mechanism we may wish to deprecate and eventually remove that mechanism. | |||
|
|||
The `token-id` must be 6 characters and the `token-secret` must be 16 characters. They must be lower case ASCII letters and numbers. Specifically it must match the regular expression: `[a-z0-9]{6}\.[a-z0-9]{16}`. There is no strong reasoning behind this beyond the history of how this has been implemented in alpha versions. | |||
|
|||
#### NEW: Bootstrap Token Secrets | |||
|
|||
Bootstrap tokens are stored and managed via Kubernetes secrets in the `kube-system` namespace. They have type `bootstrap.kubernetes.io/token`. |
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.
Is the plan to move this to an API type? Can we do it now?
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.
I wanted to start as an API type from the start but after a lot of discussion over many months we settled on this. You can read the super long gory history here: kubernetes/kubernetes#30707
@@ -130,6 +134,8 @@ The first part of the token is the `token-id`. The second part is the `token-se | |||
|
|||
This new type of token is different from the current CSV token authenticator that is currently part of Kubernetes. The CSV token authenticator requires an update on disk and a restart of the API server to update/delete tokens. As we prove out this token mechanism we may wish to deprecate and eventually remove that mechanism. |
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.
I agree that we should make tokens dynamic. But I am unclear why bootstrap tokens are different from "normal" auth tokens?
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.
Normal auth tokens are defined in a CSV file on disk. We are scoping this mechanism to just bootstrap as we are storing the tokens in etcd. The general recommended approach is, for day to day usage, use TLS client certs or another more full featured auth provider.
|
||
### Method: HTTPS Endpoint | ||
|
||
If the ClusterInfo information is hosted in a trusted place via HTTPS you can just request it that way. This will use the root certificates that are installed on the system. It may or may not be appropriate based on the user's constraints. | ||
If the ClusterInfo information is hosted in a trusted place via HTTPS you can just request it that way. This will use the root certificates that are installed on the system. It may or may not be appropriate based on the user's constraints. This method MUST use HTTPS. Also, even though the payload for this URL is the `kubeconfig` format, it MUST NOT contain client credentials. |
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.
MUST NOT
feels wrong - how can we lock down the discovery endpoint? Shouldn't this be up to RBAC policy?
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.
This is just for "cluster info" stuff. (see the section defining that) Ways to identify where the cluster is and the CA bundle. This is generally ~public information and so is stored and (with the --discovery-url
flag can be served in something like a gist if users choose.
Because this isn't often secured, we are specifying that you MUST NOT put credentials in there.
My ideal (and a previous iteration of this design) would be to not re-use the kubeconfig format and define something that just has cluster location information. But we agreed on not inventing a new config file format that overlaps the current kubeconfig.
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.
It feels very odd to prevent people from doing something more secure than what you imagine.
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.
I think you misunderstand.
If you already have credentials and a kubeconfig you don't need to use the bootstrap mechanism at all. We'll be able to skip that phase.
But if you don't already have a kubeconfig kubeadm supports the following flow:
- Discovery where the cluster is and what root CA bundles to use
- Use some temporary credentials to submit a CSR to the cert API
This is about how we do those. One method of doing (1) above is to hit a well known https endpoint. In that case, if users accidentally upload a kubeconfig with credentials then they are exposing those more widely than they should.
@@ -100,7 +104,7 @@ The user experience for joining a cluster would be something like: | |||
kubeadm join --token=ae23dc.faddc87f5a5ab458 <address> | |||
``` | |||
|
|||
**Note:** This is logically a different use of the token from TLS bootstrap. We harmonize these usages and allow the same token to play double duty. | |||
**Note:** This is logically a different use of the token used for authentication for TLS bootstrap. We harmonize these usages and allow the same token to play double duty. | |||
|
|||
#### Implementation Flow | |||
|
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.
(Commenting here as github won't let me comment on the other lines)
- Implementation note: the API server doesn't have to expose a new and special insecure HTTP endpoint.
kubeadm
requests a ConfigMap containing the kubeconfig file defined above.
- This ConfigMap exists at a well known URL:
https://<server>/api/v1/namespaces/kube-public/configmaps/cluster-info
- This ConfigMap is really public. Users don't need to authenticate to read this ConfigMap. In fact, the client MUST NOT use a bearer token here as we don't trust this endpoint yet.
This is not a good idea. Unauthenticated access to the same endpoint always evolves into a security vulnerability.
I presume this is not enabled by default, or that it can be turned off? Have the k8s security folk signed off on this?
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.
Yes -- approved and in the merge queue. See kubernetes/kubernetes#41281
@luxas @mikedanese Last call on this one. I'm going to merge at EOD as it looks like most comments have been addressed and implementation is well under way. We talked about this at the lifecycle meeting on Tuesday too. |
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.
@jbeda That sounds good to me.
You don't have to do it in this PR, but long-term we should think about how to evolve the content that's exposed, which currently is a KubeConfig v1 file.
Let's imagine we invent KubeConfig v2 tomorrow -- then we should claim the kubeconfigv2
key on the cluster-info ConfigMap I think, and sign JWT tokens based on that one with prefix jwt-kubeconfigv2-
or so... I guess that makes sense, right?
If a there is a server that exposes only the kubeconfigv2
key(s), then older consumer clients like kubeadm v1.6 won't be able to talk to it, but I suppose newer clients (kubeadm v1.9) will be able to talk to older servers that expose kubeconfig
(v1) only, given we choose so.
Some words on that -- how we are planning to upgrade this signing stuff to something that's better at exposing information about the cluster than KubeConfig v1 might be good, but that's not a blocker for me.
Thanks for reworking this, it's much better now 👍
Automatic merge from submit-queue (batch tested with PRs 41540, 41808, 41710, 41838, 41840) kubeadm: update token to use '.' in discovery pkg **What this PR does / why we need it**: While working on getting kubernetes/community#381 implemented, I noticed the kubeadm discovery pkg was printing out tokens incorrectly. Corrected and fixed up corresponding test. **Special notes for your reviewer**: /cc @luxas @jbeda **Release note**: ```release-note NONE ```
* `--discovery-file` If set, this will load the cluster-info from a file. | ||
* `--discovery-token` If set, (or set via `--token`) then we will be using the token scheme described above. | ||
* `--tls-bootstrap-token` (not officially part of this spec) This sets the token used to temporarily authenticate to the API server in order to submit a CSR for signing. If `--insecure-experimental-approve-all-kubelet-csrs-for-group` is set to `system:bootstrappers` then these CSRs will be approved automatically for a hands off joining flow. | ||
|
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.
When specifying some kind of token, one also have to specify where the master is located.
Proposed flag for that --masters
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.
Github glitch made me miss this before I merged. Needed to reload page.
But I think we talked about this on slack and decided to keep the CLI flags consistent with current behavior where the master to contact with the token isn't behind a flag.
Merged -- thanks for all the feedback. As for kubeconfig v2 -- I totally agree. I'd love to evolve that forward. Part of the early negotiation around this was to not boil the ocean and start with current kubeconfig even though it might be sub-optimal. |
…ate_2 Automatic merge from submit-queue (batch tested with PRs 41921, 41695, 42139, 42090, 41949) kubeadm: join ux changes **What this PR does / why we need it**: Update `kubeadm join` UX according to kubernetes/community#381 **Which issue this PR fixes**: fixes # kubernetes/kubeadm#176 **Special notes for your reviewer**: /cc @luxas @jbeda **Release note**: ```release-note NONE ```
Update bootstrap design doc with kubeadm UX
Update bootstrap design doc with kubeadm UX
Signed-off-by: Joe Beda joe.github@bedafamily.com