-
Notifications
You must be signed in to change notification settings - Fork 202
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
Approve Kubelet serving CSR #7
Conversation
/assign @mikedanese |
It looks like k8s-ci-robot in this repo doesn't run the tests? cc @calebamiles |
Can you do the vendor changes in a separate commit? |
e4fd823
to
d94a8b5
Compare
Remove cluster membership check. Only match csr.Username to CN and check (DNS name + IP address) against GCE API. Split into vendoring and code commits. PTAL @mikedanese |
return false | ||
} | ||
if csr.Spec.Username != x509cr.Subject.CommonName { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
func validateNodeServerCert(opts approverOptions, csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool { | ||
client, err := google.DefaultClient(context.Background()) |
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.
Does this have a global token source or do we create one per call?
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.
Good point, this was creating one per call.
Switched to a single shared token source.
for _, iface := range inst.NetworkInterfaces { | ||
for _, ip := range x509cr.IPAddresses { | ||
if ip.String() == iface.NetworkIP { | ||
return nil |
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.
why return? don't we meed to validate all ips in the CSR, not just the first?
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're right.
For some reason I thought "if any dns/ip combo passes it's sufficient". Brain fart.
a.zone = strings.TrimSpace(string(val)) | ||
|
||
// Get the token source for Application Default Credentials. | ||
a.tokenSource, err = google.DefaultTokenSource(context.Background()) |
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.
Need an AltTokenSource on masters. URLs are in the GCEConfig.
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.
Done. Is this for quota or permission reasons?
a.projectID = gceConfig.Global.ProjectID | ||
|
||
// Get cluster zone from metadata server. | ||
req, err := http.NewRequest(http.MethodGet, "http://metadata.google.internal/computeMetadata/v1/instance/attributes/cluster-location", nil) |
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 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.
Done
validators []csrValidator | ||
} | ||
|
||
func newGKEApprover(client clientset.Interface) *gkeApprover { | ||
func newGKEApprover(opts approverOptions, client clientset.Interface) *gkeApprover { | ||
return &gkeApprover{client: client, validators: validators} |
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.
save opts?
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.
Oops, done
name: "kubelet server certificate SubjectAccessReview", | ||
recognize: isNodeServerCert, | ||
validate: validateNodeServerCert, | ||
permission: authorization.ResourceAttributes{Group: "certificates.k8s.io", Resource: "certificatesigningrequests", Verb: "create", Subresource: "selfnodeserver"}, |
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.
We might want to lump this with selfnodeclient. I'm not sure if the separate permission buys us 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.
Done
|
||
outer: | ||
for _, n := range x509cr.DNSNames { | ||
inst, err := compute.NewInstancesService(cs).Get(opts.projectID, opts.zone, n).Do() |
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.
Move this out of the loop
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.
Done
if len(x509cr.DNSNames) == 0 { | ||
return errors.New("no SAN DNS names") | ||
} | ||
if len(x509cr.IPAddresses) == 0 { |
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.
Also consider if len(dnsNames) + len(ipAddrs) == 0 { return err }
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.
Also consider capping the number of SANs at something more than reasonable, e.g. 10 or 20.
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.
Also only allow dns and ip sans.
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.
Done
if err != nil { | ||
return a, err | ||
} | ||
a.zone = strings.TrimSpace(zone) |
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 isn't always going to be a zone. If it's a regional cluster, this will be a region.
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.
Done.
Loading list of all zones at startup and matching to location by prefix.
If it's a zone, only one will match.
If it's a region, all zones in it will match via prefix in name.
PTAL @mikedanese |
for _, n := range x509cr.DNSNames { | ||
inner: | ||
for _, z := range opts.zones { | ||
inst, err := srv.Get(opts.projectID, z, n).Do() |
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.
Can you use the common name to find the instance, and find the instance outside the loop?
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 we need to loop over zones regardless. The checks above limit how many requests we make.
Changed cluster zones lookup to use VM's region instead of cluster-location. |
numDNS, numIPs := len(x509cr.DNSNames), len(x509cr.IPAddresses) | ||
switch { | ||
case numDNS != numIPs: | ||
return fmt.Errorf("got %d DNS names and %d IPs, want equal numbers", numDNS, numIPs) |
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 not sure this makes sense. There's aren't 1 to 1.
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.
Done.
Also changed the check below to disallow len(IPs) > 0 && len(DNS) == 0 (yay unit tests!)
Validate same as client CSRs, plus check DNSNames/IPAddresses SAN. DNSNames should contain GCE VM name and IPAddresses should match that VM's IP. Approver checks it against cluster project in VM metadata (customer project, not master project).
Squashed all the fixes. PTAL |
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mikedanese 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 |
UPSTREAM: <carry>: GCP CCM fixes
Validate same as client CSRs, plus check DNSNames SAN.
DNSNames should contain GCE VM name. Approver checks it against cluster
project in VM metadata (customer project, not master project).
Optionally it also checks that VM belongs to cluster's NodePool.