Skip to content

Commit 0eb398c

Browse files
authored
Enable RBAC support in Kubernetes API server (#499)
* Enable RBAC and improve cert gen code * Add integration test for RBAC * Set RBAC proposal state to implemented
1 parent 8eb1e11 commit 0eb398c

File tree

14 files changed

+175
-111
lines changed

14 files changed

+175
-111
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{ kubernetes_admin_password }},admin,1
1+
{{ kubernetes_admin_password }},admin,1,"system:masters"

ansible/roles/kube-apiserver/templates/kube-apiserver.service.debug

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ExecStart={{ bin_dir }}/kube-apiserver \
99
--allow-privileged=true \
1010
--anonymous-auth=false \
1111
--apiserver-count={{ kubernetes_master_apiserver_count }} \
12-
--authorization-mode=ABAC \
12+
--authorization-mode=RBAC,ABAC \
1313
--authorization-policy-file={{ kubernetes_authorization_policy_path }} \
1414
--basic-auth-file={{ kubernetes_basic_auth_path }} \
1515
--bind-address=0.0.0.0 \

ansible/roles/kube-apiserver/templates/kube-apiserver.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ spec:
2222
- --allow-privileged=true
2323
- --anonymous-auth=false
2424
- --apiserver-count={{ kubernetes_master_apiserver_count }}
25-
- --authorization-mode=ABAC
25+
- --authorization-mode=RBAC,ABAC
2626
- --authorization-policy-file={{ kubernetes_authorization_policy_path }}
2727
- --basic-auth-file={{ kubernetes_basic_auth_path }}
2828
- --bind-address=0.0.0.0

integration/install_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ var _ = Describe("kismatic", func() {
193193
return verifyNetworkPolicy(nodes.master[0], sshKey)
194194
})
195195

196+
sub.It("should allow creating RBAC policy", func() error {
197+
// Run on worker because master uses unauth API endpoint (i.e. localhost:8080)
198+
return verifyRBAC(nodes.worker[0], sshKey)
199+
})
200+
196201
// This test should always be last
197202
sub.It("should still be a highly available cluster after removing a master node", func() error {
198203
By("Removing a Kubernetes master node")

integration/rbac.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package integration
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
)
8+
9+
func verifyRBAC(master NodeDeets, sshKey string) error {
10+
// copy rbac policy over to master node
11+
err := copyFileToRemote("test-resources/rbac/pod-reader.yaml", "/tmp/pod-reader.yaml", master, sshKey, 10*time.Second)
12+
if err != nil {
13+
return err
14+
}
15+
// we need the CA key to generate a new user cert
16+
err = copyFileToRemote("generated/keys/ca-key.pem", "/tmp/ca-key.pem", master, sshKey, 10*time.Second)
17+
if err != nil {
18+
return err
19+
}
20+
// create using kubectl
21+
commands := []string{
22+
// create the RBAC policy
23+
"sudo kubectl create -f /tmp/pod-reader.yaml",
24+
// generate a private key for jane
25+
"sudo openssl genrsa -out /tmp/jane-key.pem 2048",
26+
// generate a CSR for jane
27+
"sudo openssl req -new -key /tmp/jane-key.pem -out /tmp/jane-csr.pem -subj \"/CN=jane/O=some-group\"",
28+
// generate certificate for jane
29+
"sudo openssl x509 -req -in /tmp/jane-csr.pem -CA /etc/kubernetes/ca.pem -CAkey /tmp/ca-key.pem -CAcreateserial -out /tmp/jane.pem -days 10",
30+
// configure new user in kubeconfig
31+
"sudo kubectl config set-credentials jane --client-certificate=/tmp/jane.pem --client-key=/tmp/jane-key.pem",
32+
}
33+
err = runViaSSH(commands, []NodeDeets{master}, sshKey, 30*time.Second)
34+
if err != nil {
35+
return err
36+
}
37+
// Using kubectl to get pods should succeed
38+
err = runViaSSH([]string{"sudo kubectl get pods --user=jane"}, []NodeDeets{master}, sshKey, 30*time.Second)
39+
if err != nil {
40+
return fmt.Errorf("failed to get pods as jane: %v", err)
41+
}
42+
// This command is expected to fail, so we ignore the error.
43+
// We expect the output to contain the string "Forbidden"
44+
out, _ := executeCmd("sudo kubectl get nodes --user=jane", master.PublicIP, master.SSHUser, sshKey)
45+
if !strings.Contains(out, "Forbidden") {
46+
return fmt.Errorf("expected a forbidden response from the server, but output did not indicate this. Output was: %s", out)
47+
}
48+
return nil
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
kind: Role
3+
apiVersion: rbac.authorization.k8s.io/v1beta1
4+
metadata:
5+
namespace: default
6+
name: pod-reader
7+
rules:
8+
- apiGroups: [""] # "" indicates the core API group
9+
resources: ["pods"]
10+
verbs: ["get", "watch", "list"]
11+
---
12+
kind: RoleBinding
13+
apiVersion: rbac.authorization.k8s.io/v1beta1
14+
metadata:
15+
name: read-pods
16+
namespace: default
17+
subjects:
18+
- kind: User
19+
name: jane
20+
apiGroup: rbac.authorization.k8s.io
21+
roleRef:
22+
kind: Role
23+
name: pod-reader
24+
apiGroup: rbac.authorization.k8s.io

pkg/install/add_worker_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func (f *fakePKI) GenerateClusterCA(p *Plan) (*tls.CA, error) {
301301
f.generateCACalled = true
302302
return nil, f.err
303303
}
304-
func (f *fakePKI) GenerateClusterCertificates(p *Plan, ca *tls.CA, users []string) error { return f.err }
304+
func (f *fakePKI) GenerateClusterCertificates(p *Plan, ca *tls.CA) error { return f.err }
305305

306306
type fakeRunner struct {
307307
eventChan chan ansible.Event

pkg/install/execute.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ func (ae *ansibleExecutor) generateTLSAssets(p *Plan) error {
747747
}
748748

749749
// Generate node and user certificates
750-
err = ae.pki.GenerateClusterCertificates(p, ca, []string{"admin"})
750+
err = ae.pki.GenerateClusterCertificates(p, ca)
751751
if err != nil {
752752
return fmt.Errorf("error generating certificates for the cluster: %v", err)
753753
}

pkg/install/pki.go

+42-49
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ import (
1111
)
1212

1313
const (
14-
certOrganization = "Apprenda"
15-
certOrgUnit = "Kismatic"
16-
certCountry = "US"
17-
certState = "NY"
18-
certLocality = "Troy"
14+
certOrganization = "Apprenda"
15+
certOrgUnit = "Kismatic"
16+
certCountry = "US"
17+
certState = "NY"
18+
certLocality = "Troy"
19+
adminUser = "admin"
20+
adminGroup = "system:masters"
21+
dockerRegistryCertFilename = "docker"
22+
serviceAccountCertFilename = "service-account"
23+
serviceAccountCertCommonName = "kube-service-account"
1924
)
2025

2126
// The PKI provides a way for generating certificates for the cluster described by the Plan
@@ -25,7 +30,7 @@ type PKI interface {
2530
GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) error
2631
GetClusterCA() (*tls.CA, error)
2732
GenerateClusterCA(p *Plan) (*tls.CA, error)
28-
GenerateClusterCertificates(p *Plan, ca *tls.CA, users []string) error
33+
GenerateClusterCertificates(p *Plan, ca *tls.CA) error
2934
}
3035

3136
// LocalPKI is a file-based PKI
@@ -98,7 +103,7 @@ func (lp *LocalPKI) GenerateClusterCA(p *Plan) (*tls.CA, error) {
98103
}
99104

100105
// GenerateClusterCertificates creates a Certificates for all nodes on the cluster
101-
func (lp *LocalPKI) GenerateClusterCertificates(p *Plan, ca *tls.CA, users []string) error {
106+
func (lp *LocalPKI) GenerateClusterCertificates(p *Plan, ca *tls.CA) error {
102107
if lp.Log == nil {
103108
lp.Log = ioutil.Discard
104109
}
@@ -134,17 +139,15 @@ func (lp *LocalPKI) GenerateClusterCertificates(p *Plan, ca *tls.CA, users []str
134139
if err := lp.generateServiceAccountCert(p, ca); err != nil {
135140
return err
136141
}
137-
// Finally, create certs for user if they are missing
138-
for _, user := range users {
139-
if err := lp.generateUserCert(p, user, ca); err != nil {
140-
return err
141-
}
142+
// Create the admin user's certificate
143+
if err := lp.generateUserCert(p, ca, adminUser, []string{adminGroup}); err != nil {
144+
return err
142145
}
143146
return nil
144147
}
145148

146149
// ValidateClusterCertificates validates all certificates in the cluster
147-
func (lp *LocalPKI) ValidateClusterCertificates(p *Plan, users []string) (warn []error, err []error) {
150+
func (lp *LocalPKI) ValidateClusterCertificates(p *Plan) (warn []error, err []error) {
148151
if lp.Log == nil {
149152
lp.Log = ioutil.Discard
150153
}
@@ -183,20 +186,18 @@ func (lp *LocalPKI) ValidateClusterCertificates(p *Plan, users []string) (warn [
183186
if err != nil {
184187
err = append(err, saErr)
185188
}
186-
// Finally, create certs for user if they are missing
187-
for _, user := range users {
188-
_, userWarn, userErr := lp.validateUserCert(user)
189-
warn = append(warn, userWarn...)
190-
if err != nil {
191-
err = append(err, userErr)
192-
}
189+
// Validate admin certificate
190+
_, userWarn, userErr := lp.validateUserCert(adminUser, []string{adminGroup})
191+
warn = append(warn, userWarn...)
192+
if err != nil {
193+
err = append(err, userErr)
193194
}
194195
return warn, err
195196
}
196197

197198
// GenerateNodeCertificate creates a private key and certificate for the given node
198199
func (lp *LocalPKI) GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) error {
199-
CN := node.Host
200+
commonName := node.Host
200201
// Build list of SANs
201202
clusterSANs, err := clusterCertsSubjectAlternateNames(plan)
202203
if err != nil {
@@ -216,7 +217,7 @@ func (lp *LocalPKI) GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) e
216217
}
217218

218219
// Don't generate if the key pair exists and valid
219-
valid, warn, err := tls.CertExistsAndValid(CN, nodeSANs, node.Host, lp.GeneratedCertsDirectory)
220+
valid, warn, err := tls.CertExistsAndValid(commonName, nodeSANs, []string{}, node.Host, lp.GeneratedCertsDirectory)
220221
if err != nil {
221222
return err
222223
}
@@ -232,7 +233,7 @@ func (lp *LocalPKI) GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) e
232233

233234
util.PrettyPrintOk(lp.Log, "Generating certificates for host %q", node.Host)
234235

235-
key, cert, err := generateCert(CN, plan, nodeSANs, ca)
236+
key, cert, err := generateCert(ca, commonName, nodeSANs)
236237
if err != nil {
237238
return fmt.Errorf("error during cluster cert generation: %v", err)
238239
}
@@ -263,17 +264,17 @@ func (lp *LocalPKI) validateNodeCertificate(p *Plan, node Node) (valid bool, war
263264
}
264265
}
265266

266-
return tls.CertExistsAndValid(CN, nodeSANs, node.Host, lp.GeneratedCertsDirectory)
267+
return tls.CertExistsAndValid(CN, nodeSANs, []string{}, node.Host, lp.GeneratedCertsDirectory)
267268
}
268269

269270
func (lp *LocalPKI) generateDockerRegistryCert(p *Plan, ca *tls.CA) error {
270271
// Default registry will be deployed on the first master
271272
n := p.Master.Nodes[0]
272-
CN := n.Host
273+
commonName := n.Host
273274
SANs := []string{n.Host, n.IP, n.InternalIP}
274275

275276
// Don't generate if the key pair exists and valid
276-
valid, warn, err := tls.CertExistsAndValid(CN, SANs, "docker", lp.GeneratedCertsDirectory)
277+
valid, warn, err := tls.CertExistsAndValid(commonName, SANs, []string{}, dockerRegistryCertFilename, lp.GeneratedCertsDirectory)
277278
if err != nil {
278279
return err
279280
}
@@ -289,11 +290,11 @@ func (lp *LocalPKI) generateDockerRegistryCert(p *Plan, ca *tls.CA) error {
289290

290291
util.PrettyPrintOk(lp.Log, "Generating certificates for docker registry")
291292

292-
dockerKey, dockerCert, err := generateCert(CN, p, SANs, ca)
293+
dockerKey, dockerCert, err := generateCert(ca, commonName, SANs)
293294
if err != nil {
294295
return fmt.Errorf("error during user cert generation: %v", err)
295296
}
296-
err = tls.WriteCert(dockerKey, dockerCert, "docker", lp.GeneratedCertsDirectory)
297+
err = tls.WriteCert(dockerKey, dockerCert, dockerRegistryCertFilename, lp.GeneratedCertsDirectory)
297298
if err != nil {
298299
return fmt.Errorf("error writing cert files for docker registry")
299300
}
@@ -306,16 +307,13 @@ func (lp *LocalPKI) validateDockerRegistryCert(p *Plan) (valid bool, warn []erro
306307
CN := n.Host
307308
SANs := []string{n.Host, n.IP, n.InternalIP}
308309

309-
return tls.CertExistsAndValid(CN, SANs, "docker", lp.GeneratedCertsDirectory)
310+
return tls.CertExistsAndValid(CN, SANs, []string{}, dockerRegistryCertFilename, lp.GeneratedCertsDirectory)
310311
}
311312

312313
func (lp *LocalPKI) generateServiceAccountCert(p *Plan, ca *tls.CA) error {
313-
CN := "kube-service-account"
314314
SANs := []string{}
315-
certName := "service-account"
316-
317315
// Don't generate if the key pair exists and valid
318-
valid, warn, err := tls.CertExistsAndValid(CN, SANs, certName, lp.GeneratedCertsDirectory)
316+
valid, warn, err := tls.CertExistsAndValid(serviceAccountCertCommonName, SANs, []string{}, serviceAccountCertFilename, lp.GeneratedCertsDirectory)
319317
if err != nil {
320318
return err
321319
}
@@ -328,32 +326,28 @@ func (lp *LocalPKI) generateServiceAccountCert(p *Plan, ca *tls.CA) error {
328326
util.PrettyPrintOk(lp.Log, "Found key and certificate for service accounts")
329327
return nil
330328
}
331-
332329
util.PrettyPrintOk(lp.Log, "Generating certificates for service accounts")
333330

334-
key, cert, err := generateCert(CN, p, SANs, ca)
331+
key, cert, err := generateCert(ca, serviceAccountCertCommonName, SANs)
335332
if err != nil {
336333
return fmt.Errorf("error generating service account certs: %v", err)
337334
}
338-
if err = tls.WriteCert(key, cert, certName, lp.GeneratedCertsDirectory); err != nil {
335+
if err = tls.WriteCert(key, cert, serviceAccountCertFilename, lp.GeneratedCertsDirectory); err != nil {
339336
return fmt.Errorf("error writing generated service account cert: %v", err)
340337
}
341338
return nil
342339
}
343340

344341
func (lp *LocalPKI) validateServiceAccountCert() (valid bool, warn []error, err error) {
345-
CN := "kube-service-account"
346342
SANs := []string{}
347-
certName := "service-account"
348-
349-
return tls.CertExistsAndValid(CN, SANs, certName, lp.GeneratedCertsDirectory)
343+
return tls.CertExistsAndValid(serviceAccountCertCommonName, SANs, []string{}, serviceAccountCertFilename, lp.GeneratedCertsDirectory)
350344
}
351345

352-
func (lp *LocalPKI) generateUserCert(p *Plan, user string, ca *tls.CA) error {
346+
func (lp *LocalPKI) generateUserCert(p *Plan, ca *tls.CA, user string, groups []string) error {
353347
SANs := []string{user}
354348

355349
// Don't generate if the key pair exists and valid
356-
valid, warn, err := tls.CertExistsAndValid(user, SANs, user, lp.GeneratedCertsDirectory)
350+
valid, warn, err := tls.CertExistsAndValid(user, SANs, groups, user, lp.GeneratedCertsDirectory)
357351
if err != nil {
358352
return err
359353
}
@@ -369,7 +363,7 @@ func (lp *LocalPKI) generateUserCert(p *Plan, user string, ca *tls.CA) error {
369363

370364
util.PrettyPrintOk(lp.Log, "Generating certificates for user %q", user)
371365

372-
adminKey, adminCert, err := generateCert(user, p, SANs, ca)
366+
adminKey, adminCert, err := generateCert(ca, user, SANs, groups...)
373367
if err != nil {
374368
return fmt.Errorf("error during user cert generation: %v", err)
375369
}
@@ -380,15 +374,14 @@ func (lp *LocalPKI) generateUserCert(p *Plan, user string, ca *tls.CA) error {
380374
return nil
381375
}
382376

383-
func (lp *LocalPKI) validateUserCert(user string) (valid bool, warn []error, err error) {
377+
func (lp *LocalPKI) validateUserCert(user string, groups []string) (valid bool, warn []error, err error) {
384378
SANs := []string{user}
385-
386-
return tls.CertExistsAndValid(user, SANs, user, lp.GeneratedCertsDirectory)
379+
return tls.CertExistsAndValid(user, SANs, groups, user, lp.GeneratedCertsDirectory)
387380
}
388381

389-
func generateCert(cnName string, p *Plan, hostList []string, ca *tls.CA) (key, cert []byte, err error) {
382+
func generateCert(ca *tls.CA, commonName string, hostList []string, organizations ...string) (key, cert []byte, err error) {
390383
req := csr.CertificateRequest{
391-
CN: cnName,
384+
CN: commonName,
392385
KeyRequest: &csr.BasicKeyRequest{
393386
A: "rsa",
394387
S: 2048,
@@ -406,7 +399,7 @@ func generateCert(cnName string, p *Plan, hostList []string, ca *tls.CA) (key, c
406399
}
407400
key, cert, err = tls.NewCert(ca, req)
408401
if err != nil {
409-
return nil, nil, fmt.Errorf("error generating certs for %q: %v", cnName, err)
402+
return nil, nil, fmt.Errorf("error generating certs for %q: %v", commonName, err)
410403
}
411404
return key, cert, err
412405
}

0 commit comments

Comments
 (0)