Skip to content

Commit a43820e

Browse files
author
Matt Rogers
committed
Add bootstrap phase check
1 parent 68412e5 commit a43820e

File tree

2 files changed

+69
-58
lines changed

2 files changed

+69
-58
lines changed

csr_check.go

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,58 +17,33 @@ const (
1717
nodeUserPrefix = nodeUser + ":"
1818
)
1919

20-
// authorizeCSR authorizes the CertificateSigningRequest req for a node's server certificate.
21-
// csr should be the parsed CSR from req.Spec.Request. Names contained in the CSR are checked against addresses in the
22-
// corresponding node's machine status.
23-
func authorizeCSR(machineList *capiclient.MachineList, req *certificatesv1beta1.CertificateSigningRequest, csr *x509.CertificateRequest) bool {
24-
if machineList == nil || len(machineList.Items) < 1 || req == nil || csr == nil {
25-
return false
20+
func validateCSRContents(req *certificatesv1beta1.CertificateSigningRequest, csr *x509.CertificateRequest) (string, bool) {
21+
if !strings.HasPrefix(req.Spec.Username, nodeUserPrefix) {
22+
return "",false
2623
}
2724

28-
csrSubject := csr.Subject
29-
csrIPSan := csr.IPAddresses
30-
csrDNSSan := csr.DNSNames
31-
32-
// Check username: system:node:ip-10-0-152-205.ec2.internal
33-
userName := req.Spec.Username
34-
if !strings.HasPrefix(userName, nodeUserPrefix) {
35-
return false
36-
}
37-
38-
nodeAsking := strings.TrimPrefix(userName, nodeUserPrefix)
25+
nodeAsking := strings.TrimPrefix(req.Spec.Username, nodeUserPrefix)
3926
if len(nodeAsking) < 1 {
40-
return false
41-
}
42-
43-
// Check that we have a registered node with the request name
44-
var targetMachine *capiclient.MachineStatus
45-
for _, machine := range machineList.Items {
46-
if machine.Status.NodeRef != nil && machine.Status.NodeRef.Name == nodeAsking {
47-
targetMachine = machine.Status.DeepCopy()
48-
break
49-
}
50-
}
51-
if targetMachine == nil {
52-
return false
27+
return "",false
5328
}
5429

5530
// Check groups, we need at least:
5631
// - system:nodes
5732
// - system:authenticated
5833
if len(req.Spec.Groups) < 2 {
59-
return false
34+
return "",false
6035
}
6136
groupSet := sets.NewString(req.Spec.Groups...)
6237
if !groupSet.HasAll(nodeGroup, "system:authenticated") {
63-
return false
38+
return "",false
6439
}
6540

6641
// Check usages, we need only:
6742
// - digital signature
6843
// - key encipherment
6944
// - server auth
7045
if len(req.Spec.Usages) != 3 {
71-
return false
46+
return "",false
7247
}
7348

7449
usages := make([]string, 0)
@@ -78,7 +53,7 @@ func authorizeCSR(machineList *capiclient.MachineList, req *certificatesv1beta1.
7853

7954
// No extra usages!
8055
if len(usages) != 3 {
81-
return false
56+
return "",false
8257
}
8358

8459
usageSet := sets.NewString(usages...)
@@ -87,29 +62,56 @@ func authorizeCSR(machineList *capiclient.MachineList, req *certificatesv1beta1.
8762
string(certificatesv1beta1.UsageKeyEncipherment),
8863
string(certificatesv1beta1.UsageServerAuth),
8964
) {
90-
return false
65+
return "",false
9166
}
9267

9368
// Check subject: O = system:nodes, CN = system:node:ip-10-0-152-205.ec2.internal
94-
if csrSubject.CommonName != userName {
95-
return false
69+
if csr.Subject.CommonName != req.Spec.Username {
70+
return "",false
9671
}
9772

9873
var hasOrg bool
99-
for i := range csrSubject.Organization {
100-
if csrSubject.Organization[i] == nodeGroup {
74+
for i := range csr.Subject.Organization {
75+
if csr.Subject.Organization[i] == nodeGroup {
10176
hasOrg = true
10277
break
10378
}
10479
}
10580
if !hasOrg {
81+
return "",false
82+
}
83+
84+
return nodeAsking, true
85+
}
86+
87+
// authorizeCSR authorizes the CertificateSigningRequest req for a node's server certificate.
88+
// csr should be the parsed CSR from req.Spec.Request. Names contained in the CSR are checked against addresses in the
89+
// corresponding node's machine status.
90+
func authorizeCSR(machineList *capiclient.MachineList, req *certificatesv1beta1.CertificateSigningRequest, csr *x509.CertificateRequest) bool {
91+
if machineList == nil || len(machineList.Items) < 1 || req == nil || csr == nil {
92+
return false
93+
}
94+
95+
nodeAsking, ok := validateCSRContents(req, csr)
96+
if !ok {
97+
return false
98+
}
99+
// Check that we have a registered node with the request name
100+
var targetMachine *capiclient.MachineStatus
101+
for _, machine := range machineList.Items {
102+
if machine.Status.NodeRef != nil && machine.Status.NodeRef.Name == nodeAsking {
103+
targetMachine = machine.Status.DeepCopy()
104+
break
105+
}
106+
}
107+
if targetMachine == nil {
106108
return false
107109
}
108110

109111
// SAN checks for both DNS and IPs, e.g.,
110112
// DNS:ip-10-0-152-205, DNS:ip-10-0-152-205.ec2.internal, IP Address:10.0.152.205, IP Address:10.0.152.205
111113
// All names in the request must correspond to addresses assigned to a single machine.
112-
for _, san := range csrDNSSan {
114+
for _, san := range csr.DNSNames {
113115
if len(san) < 1 {
114116
continue
115117
}
@@ -129,7 +131,7 @@ func authorizeCSR(machineList *capiclient.MachineList, req *certificatesv1beta1.
129131
}
130132
}
131133

132-
for _, san := range csrIPSan {
134+
for _, san := range csr.IPAddresses {
133135
if len(san) < 1 {
134136
continue
135137
}

main.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,34 +103,43 @@ func (c *Controller) handleNewCSR(key string) error {
103103
return nil
104104
}
105105

106-
var machineListObj runtime.Object
107-
err = c.clientset.RESTClient().Get().Namespace("openshift-cluster-api").Name("machines").Do().Into(machineListObj)
108-
if err != nil {
109-
glog.Infof("error fetching machine list: %v", err)
110-
return nil
111-
}
112-
machineList, ok := machineListObj.(*capiclient.MachineList)
113-
if !ok {
114-
glog.Infof("fetched machineList but object is not the expected type")
115-
return nil
116-
}
117-
118106
parsedCSR, err := csrclient.ParseCSR(csr)
119107
if err != nil {
120108
glog.Infof("error parsing request CSR: %v", err)
121109
return nil
122110
}
123111

124-
if !authorizeCSR(machineList, csr, parsedCSR) {
125-
// Don't deny since it might be someone else's CSR
126-
glog.Infof("CSR %s not authorized", csr.GetName())
127-
return nil
112+
approvalMsg := "This CSR was approved by the Node CSR Approver"
113+
var machineListObj runtime.Object
114+
err = c.clientset.RESTClient().Get().Namespace("openshift-cluster-api").Name("machines").Do().Into(machineListObj)
115+
if err == nil {
116+
machineList, ok := machineListObj.(*capiclient.MachineList)
117+
if !ok {
118+
glog.Infof("fetched machineList but object is not the expected type")
119+
return nil
120+
}
121+
122+
if !authorizeCSR(machineList, csr, parsedCSR) {
123+
// Don't deny since it might be someone else's CSR
124+
glog.Infof("CSR %s not authorized", csr.GetName())
125+
return nil
126+
}
127+
}
128+
if err != nil {
129+
glog.Infof("machine api not available: %v", err)
130+
// Validate the CSR for the bootstrapping phase without a SAN check.
131+
_, ok := validateCSRContents(csr, parsedCSR)
132+
if !ok {
133+
glog.Infof("CSR %s not valid", csr.GetName())
134+
return nil
135+
}
136+
approvalMsg += " (no SAN validation)"
128137
}
129138

130139
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
131140
Type: certificatesv1beta1.CertificateApproved,
132141
Reason: "NodeCSRApprove",
133-
Message: "This CSR was approved by the Node CSR Approver.",
142+
Message: approvalMsg,
134143
LastUpdateTime: metav1.Now(),
135144
})
136145

0 commit comments

Comments
 (0)