Skip to content

Commit c4ba302

Browse files
Merge pull request #10 from mrogers950/csr_checking
Approve CSRs based on machine status
2 parents 2fbc6a6 + 6cadbbe commit c4ba302

File tree

1,045 files changed

+107654
-25
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,045 files changed

+107654
-25
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/machine-approver
1+
/machine-approver
2+
.idea*

csr_check.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package main
2+
3+
import (
4+
"crypto/x509"
5+
"strings"
6+
7+
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
8+
"k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/util/sets"
10+
11+
"github.com/openshift/cluster-api/pkg/apis/machine/v1beta1"
12+
)
13+
14+
const (
15+
nodeUser = "system:node"
16+
nodeGroup = "system:nodes"
17+
nodeUserPrefix = nodeUser + ":"
18+
)
19+
20+
func validateCSRContents(req *certificatesv1beta1.CertificateSigningRequest, csr *x509.CertificateRequest) (string, bool) {
21+
if !strings.HasPrefix(req.Spec.Username, nodeUserPrefix) {
22+
return "",false
23+
}
24+
25+
nodeAsking := strings.TrimPrefix(req.Spec.Username, nodeUserPrefix)
26+
if len(nodeAsking) < 1 {
27+
return "",false
28+
}
29+
30+
// Check groups, we need at least:
31+
// - system:nodes
32+
// - system:authenticated
33+
if len(req.Spec.Groups) < 2 {
34+
return "",false
35+
}
36+
groupSet := sets.NewString(req.Spec.Groups...)
37+
if !groupSet.HasAll(nodeGroup, "system:authenticated") {
38+
return "",false
39+
}
40+
41+
// Check usages, we need only:
42+
// - digital signature
43+
// - key encipherment
44+
// - server auth
45+
if len(req.Spec.Usages) != 3 {
46+
return "",false
47+
}
48+
49+
usages := make([]string, 0)
50+
for i := range req.Spec.Usages {
51+
usages = append(usages, string(req.Spec.Usages[i]))
52+
}
53+
54+
// No extra usages!
55+
if len(usages) != 3 {
56+
return "",false
57+
}
58+
59+
usageSet := sets.NewString(usages...)
60+
if !usageSet.HasAll(
61+
string(certificatesv1beta1.UsageDigitalSignature),
62+
string(certificatesv1beta1.UsageKeyEncipherment),
63+
string(certificatesv1beta1.UsageServerAuth),
64+
) {
65+
return "",false
66+
}
67+
68+
// Check subject: O = system:nodes, CN = system:node:ip-10-0-152-205.ec2.internal
69+
if csr.Subject.CommonName != req.Spec.Username {
70+
return "",false
71+
}
72+
73+
var hasOrg bool
74+
for i := range csr.Subject.Organization {
75+
if csr.Subject.Organization[i] == nodeGroup {
76+
hasOrg = true
77+
break
78+
}
79+
}
80+
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 *v1beta1.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 *v1beta1.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 {
108+
return false
109+
}
110+
111+
// SAN checks for both DNS and IPs, e.g.,
112+
// 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
113+
// All names in the request must correspond to addresses assigned to a single machine.
114+
for _, san := range csr.DNSNames {
115+
if len(san) < 1 {
116+
continue
117+
}
118+
var foundSan bool
119+
for _, addr := range targetMachine.Addresses {
120+
switch addr.Type {
121+
case v1.NodeInternalDNS, v1.NodeExternalDNS, v1.NodeHostName:
122+
if san == addr.Address {
123+
foundSan = true
124+
}
125+
default:
126+
}
127+
}
128+
// The CSR requested a DNS name that did not belong to the machine
129+
if !foundSan {
130+
return false
131+
}
132+
}
133+
134+
for _, san := range csr.IPAddresses {
135+
if len(san) < 1 {
136+
continue
137+
}
138+
var foundSan bool
139+
for _, addr := range targetMachine.Addresses {
140+
switch addr.Type {
141+
case v1.NodeInternalIP, v1.NodeExternalIP:
142+
if san.String() == addr.Address {
143+
foundSan = true
144+
}
145+
default:
146+
}
147+
}
148+
// The CSR requested an IP name that did not belong to the machine
149+
if !foundSan {
150+
return false
151+
}
152+
}
153+
154+
return true
155+
}

0 commit comments

Comments
 (0)