Skip to content
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 CSR controllers & kubelet to respect signerName field #88246

Merged
merged 3 commits into from
Feb 29, 2020

Conversation

munnerz
Copy link
Member

@munnerz munnerz commented Feb 17, 2020

What type of PR is this?
/kind feature

What this PR does / why we need it:

Adds support for the signerName field added to the CertificateSigningRequest resource as part of the changes described in kubernetes/enhancements#1400 to:

  • CSR approval controller
  • CSR signer controller
  • Kubelet TLS bootstrapping/certificate manager
  • client-go's RequestCertificate helper function.

Special notes for your reviewer:

The top commit of this branch is the one relevant to this PR 😄

This PR depends on:

Does this PR introduce a user-facing change?:

ACTION REQUIRED: consumers of the 'certificatesigningrequests/approval' API must now grant permission to 'approve' CSRs for the 'signerName' specified on the CSR. More information on the new signerName field can be found at https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/20190607-certificates-api.md#signers

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

- [KEP]: https://github.com/kubernetes/enhancements/pull/1400

/cc @enj @liggitt @deads2k

@k8s-ci-robot k8s-ci-robot added the release-note-none Denotes a PR that doesn't merit a release note. label Feb 17, 2020
@k8s-ci-robot k8s-ci-robot added kind/feature Categorizes issue or PR as related to a new feature. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. area/apiserver area/kubectl area/kubelet area/test kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/apps Categorizes an issue or PR as relevant to SIG Apps. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/node Categorizes an issue or PR as relevant to SIG Node. sig/testing Categorizes an issue or PR as relevant to SIG Testing. and removed needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Feb 17, 2020
@munnerz
Copy link
Member Author

munnerz commented Feb 17, 2020

I set release note to NONE as the first PR already adds:

  • Add 'signerName' field to the CertificateSigningRequest API to allow for external signers to be built

And the admission PR has:

  • ACTION REQUIRED: consumers of the 'certificatesigningrequests/approval' API must now grant permission to 'approve' CSRs for the 'signerName' specified on the CSR. More information on the new signerName field can be found in the KEP: add multiple signers to CSRs enhancements#1400

which seems sufficient to me 😄

Is there some way to collect release notes specific to breaking changes in client-go? This PR changes the function signature of RequestCertificate to add signerName @nikhita

@munnerz munnerz force-pushed the csr-signername-controllers branch 2 times, most recently from 5f4e078 to 6791c48 Compare February 17, 2020 17:57
@munnerz
Copy link
Member Author

munnerz commented Feb 17, 2020

@liggitt I've updated this to address your comments 😄

case capi.KubeletServingSignerName:
return capihelper.IsKubeletServingCSR(req, usages)
case capi.KubeAPIServerClientKubeletSignerName:
return capihelper.IsKubeletServingCSR(req, usages)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liggitt if the CSR does not exactly match, what should the signing controller do? It could either:

  1. override the values
  2. reject the request/mark it as invalid somehow (we don't have this option yet)
  3. display a warning to the user
  4. do nothing at all

Right now with this change we do (4), and simply return nil.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding (3) by simply firing an Event and returning nil could work, but has the potential to cause a lot of events as we have no way to recognise the terminal state of the CR.

(2) is the best solution long-term for external signers IMO, but probably needs consideration in the KEP due to the awkward design of the CSR conditions structure.

(1) is an option too - it'd require duplicating/shuffling around some of this checking logic to allow for usages to be different to what's requested by the user. It may be non-obvious though, and it isn't something that's been done in the past (old logic requires usages to be specified as-per policy). Given it's a net-addition and goes beyond what was described in the KEP changes however, I feel like it might be a bit brash to add it in here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do 4 for now. I'm looking into how to let signers indicate permanent failure in the CSR status

@fejta-bot
Copy link

This PR may require API review.

If so, when the changes are ready, complete the pre-review checklist and request an API review.

Status of requested reviews is tracked in the API Review project.

@neolit123
Copy link
Member

thanks for the heads up. i've opened:
kubernetes/kubeadm#2047

i think i digested some of the details, but have one question regarding the timeline of the kubeadm change (1.18 vs next):

  • the admission is now going to be enabled by default and a missing signerName in the CSR spec would mean fallback to the legacy signer (from defaulting). however, the legacy signer does not have auto-approval by the KCM. does that mean that kubeadm code, as is, will now timeout at the subsequent WaitForCertificate call here ?

if true this means we need to apply this change to kubeadm before CF next week and for 1.18.
wondering how this affects third party distributions that use CSR and auto-aproval.

disclaimer: i wouldn't exclude that i'm misunderstanding something.

@liggitt
Copy link
Member

liggitt commented Feb 29, 2020

CSR objects that don't have a signerName get defaulted based on the format of the request. Requests shaped like node client or serving certificates get those signerNames assigned, so auto approval flows for node client certificates continue working uninterrupted.

To support migration from v1beta1 to v1, this required field will be defaulted in v1beta1 (optional in openapi), but not defaulted and required in v1 :

  1. If it's a kubelet client certificate, it is assigned "kubernetes.io/kube-apiserver-client-kubelet".
  2. If it's a kubelet serving certificate, it is assigned "kubernetes.io/kubelet-serving". see https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/controller/certificates/approver/sarapprove.go#L211-L223 for details.
  3. Otherwise, it is assigned "kubernetes.io/legacy-unknown".

@neolit123
Copy link
Member

CSR objects that don't have a signerName get defaulted based on the format of the request. Requests shaped like node client or serving certificates get those signerNames assigned, so auto approval flows for node client certificates continue working uninterrupted.

ok, thanks for the clarification.

@liggitt
Copy link
Member

liggitt commented Feb 29, 2020

I'll take #88246 (comment) as a follow up, would like to get this soaking ahead of freeze next week

/lgtm
/approve

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: liggitt, munnerz

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 29, 2020
@enj
Copy link
Member

enj commented Feb 29, 2020

/hold cancel

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Feb 29, 2020
@k8s-ci-robot k8s-ci-robot merged commit 03b7f27 into kubernetes:master Feb 29, 2020
@k8s-ci-robot k8s-ci-robot added this to the v1.18 milestone Feb 29, 2020
l.Insert(string(s))
}
r := sets.NewString()
for _, s := range right {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there is no need to create the second Set.
Checking sizes of the slices and whether each of the second slice is contained in the first Set should suffice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would return true for [a, b, c] vs [a, a, b]

Copy link
Contributor

@tedyu tedyu Feb 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following would achieve same semantics with one map.

func equalUnsorted(left, right []certificatesv1beta1.KeyUsage) bool {
	var l map[string]int
	l = make(map[string]int)
	for _, s := range left {
		k := string(s)
		cnt := l[k]
		l[k] = cnt + 1
	}
	for _, s := range right {
		k := string(s)
		cnt, ok := l[k]
		if !ok {
			return false
		}
		cnt--
		if cnt == 0 {
			delete(l, k)
		} else {
			l[k] = cnt
		}
	}
	return len(l) == 0
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we really want to be cheap:

func equalUnsorted(left, right []certificatesv1beta1.KeyUsage) bool {
	if len(left) != len(right) {
		return false
	}
	l := make(map[string]int, len(left))
	for _, s := range left {
		k := string(s)
		l[k] = l[k] + 1
	}
	for _, s := range right {
		k := string(s)
		c := l[k]
		if c == 0 {
			return false
		}
		l[k] = (c - 1)
	}
	return true
}

but we do use the sets API all over, I'm not sure if this change is desirable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

	if len(left) != len(right) {
		return false
	}

We shouldn't use the above check since there may be duplicate in either side.

		c := l[k]
		if c == 0 {

The decrement should be done before checking c against 0.

I prefer the formation I presented above.
Set is not expressive in this use case. We don't have to use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't use the above check since there may be duplicate in either side.

If we're not checking for identical counts in each side, why even count in the map ...?

The decrement should be done before checking c against 0.

No, if we decrement to zero then we've had an exact match. If we find an entry with zero on the next check we did not. I did test this code 🙃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @BenTheElder for looking at my code.

Before we dig deeper into the code, can we get agreement on whether the following are considered equal:

[a, b] vs [a, a, b]

Along the current code, they're equal.
That was why I said we shouldn't check the lengths of two sides.

Copy link
Contributor

@tedyu tedyu Feb 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenTheElder was right in that the value of the map shouldn't be the number of times we see the same key from the left.
Here is revised code which keeps the current semantics and only uses one map where value of 1 represents 'seen from the left' and value of -1 means 'seen from the right' (regardless the number of times seen):

func equalUnsorted(left, right []certificatesv1beta1.KeyUsage) bool {
	var l map[string]int
	l = make(map[string]int)
	for _, s := range left {
		k := string(s)
		l[k] = 1
	}
	count := 0
	for _, s := range right {
		k := string(s)
		val, ok := l[k]
		if !ok {
			// not equal if we haven't seen this key from the left
			return false
		}
		if val == 1 {
			count++
			// we don't want to delete the key since there may be more occurrence of this key in right
			l[k] = -1
		}
	}
	// if we have seen all the keys from the right, they're equal
	return len(l) == count
}

I can refine the comment in the code if there is question on how it works or whether it is correct.

Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep this on the back burner and look out for frequent calls to similar equality checks.

}
if !requestValidForSignerName(x509cr, csr.Spec.Usages, *csr.Spec.SignerName) {
// TODO: mark the CertificateRequest as being in a terminal state and
// communicate to the user why the request has been refused.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is an error return suitable for conveying that the CertificateRequest is in terminal state ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a place to record it on the API object for that to be effective. I'm working on a well-defined condition to communicate that.

@BenTheElder
Copy link
Member

BenTheElder commented Mar 1, 2020 via email

@mikedanese
Copy link
Member

+1 to @BenTheElder's comment. We should avoid premature optimization that negatively impact readability and maintainability.

@errordeveloper
Copy link
Member

Amazing, thanks a lot @munnerz!

@k8s-ci-robot k8s-ci-robot added release-note-action-required Denotes a PR that introduces potentially breaking changes that require user action. and removed release-note-none Denotes a PR that doesn't merit a release note. labels Mar 11, 2020
@liggitt liggitt added the release-note-none Denotes a PR that doesn't merit a release note. label Mar 11, 2020
@k8s-ci-robot k8s-ci-robot removed the release-note-none Denotes a PR that doesn't merit a release note. label Mar 11, 2020
marun added a commit to marun/cluster-machine-approver that referenced this pull request Mar 27, 2020
New permissions are required to approve CSRs in 1.18, as per [1].

1: kubernetes/kubernetes#88246
wking pushed a commit to wking/kubernetes that referenced this pull request Jul 21, 2020
…ollers

Update CSR controllers & kubelet to respect signerName field

Kubernetes-commit: 03b7f27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/apiserver area/kubectl area/kubelet area/test cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note-action-required Denotes a PR that introduces potentially breaking changes that require user action. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/apps Categorizes an issue or PR as relevant to SIG Apps. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/node Categorizes an issue or PR as relevant to SIG Node. sig/testing Categorizes an issue or PR as relevant to SIG Testing. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Development

Successfully merging this pull request may close these issues.

None yet

10 participants