blob: 3386625e1ca6d9ce09f7eed4286e174c7ec2ac74 [file] [log] [blame]
Sergiusz Bazanskib13b7ff2019-08-29 20:12:24 +02001package main
2
3import (
4 "encoding/pem"
5 "fmt"
6 "time"
7
8 "github.com/golang/glog"
9 corev1 "k8s.io/api/core/v1"
10 rbacv1 "k8s.io/api/rbac/v1"
11 "k8s.io/apimachinery/pkg/api/errors"
12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 "k8s.io/client-go/kubernetes"
14 "k8s.io/client-go/rest"
15
16 pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
17)
18
19func (p *prodvider) kubernetesCreds(username string) (*pb.KubernetesKeys, error) {
20 o := fmt.Sprintf("sso:%s", username)
21
22 csrPEM, keyPEM, err := p.makeKubernetesCSR(username+"@hackerspace.pl", o)
23 if err != nil {
24 return nil, err
25 }
26
27 certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(13*time.Hour))
28 if err != nil {
29 return nil, err
30 }
31
32 caCert, _ := p.sign.Certificate("", "")
33 caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
34
35 // Build certificate chain from new cert and intermediate CA.
36 chainPEM := append(certPEM, caPEM...)
37
38 glog.Infof("Generated k8s certificate for %q", username)
39 return &pb.KubernetesKeys{
40 Cluster: "k0.hswaw.net",
41 // APIServerCA
42 Ca: p.kubeCAPEM,
43 // Chain of new cert + intermediate CA
44 Cert: chainPEM,
45 Key: keyPEM,
46 }, nil
47}
48
49func (p *prodvider) kubernetesConnect() error {
50 csrPEM, keyPEM, err := p.makeKubernetesCSR("prodvider", "system:masters")
51 if err != nil {
52 return err
53 }
54
55 certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(30*24*time.Hour))
56 if err != nil {
57 return err
58 }
59
60 caCert, _ := p.sign.Certificate("", "")
61
62 caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
63
64 glog.Infof("Generated k8s certificate for self (system:masters)")
65
66 // Build certificate chain from our cert and intermediate CA.
67 chainPEM := append(certPEM, caPEM...)
68
69 config := &rest.Config{
70 Host: flagKubernetesHost,
71 TLSClientConfig: rest.TLSClientConfig{
72 // Chain to authenticate ourselves (us + intermediate CA).
73 CertData: chainPEM,
74 KeyData: keyPEM,
75 // APIServer CA for verification.
76 CAData: p.kubeCAPEM,
77 },
78 }
79
80 cs, err := kubernetes.NewForConfig(config)
81 if err != nil {
82 return err
83 }
84
85 p.k8s = cs
86
87 return nil
88}
89
90// kubernetesSetupUser ensures that for a given SSO username we:
91// - have a personal-<username> namespace
92// - have a sso:<username>:personal rolebinding that binds
93// system:admin-namespace to the user within their personal namespace
94// - have a sso:<username>:global clusterrolebinding that binds
95// system:viewer to the user at cluster level
96func (p *prodvider) kubernetesSetupUser(username string) error {
97 namespace := "personal-" + username
98 if err := p.ensureNamespace(namespace); err != nil {
99 return err
100 }
101 if err := p.ensureRoleBindingPersonal(namespace, username); err != nil {
102 return err
103 }
104 if err := p.ensureClusterRoleBindingGlobal(username); err != nil {
105 return err
106 }
107
108 return nil
109}
110
111func (p *prodvider) ensureNamespace(name string) error {
112 _, err := p.k8s.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
113 switch {
114 case err == nil:
115 // Already exists, nothing to do
116 return nil
117 case errors.IsNotFound(err):
118 break
119 default:
120 // Something went wrong.
121 return err
122 }
123 ns := &corev1.Namespace{
124 ObjectMeta: metav1.ObjectMeta{
125 Name: name,
126 },
127 }
128 _, err = p.k8s.CoreV1().Namespaces().Create(ns)
129 return err
130}
131
132func (p *prodvider) ensureRoleBindingPersonal(namespace, username string) error {
133 name := "sso:" + username + ":personal"
134 rb := &rbacv1.RoleBinding{
135 ObjectMeta: metav1.ObjectMeta{
136 Name: name,
137 Namespace: namespace,
138 },
139 Subjects: []rbacv1.Subject{
140 {
141 APIGroup: "rbac.authorization.k8s.io",
142 Kind: "User",
143 Name: username + "@hackerspace.pl",
144 },
145 },
146 RoleRef: rbacv1.RoleRef{
147 APIGroup: "rbac.authorization.k8s.io",
148 Kind: "ClusterRole",
149 Name: "system:admin-namespace",
150 },
151 }
152
153 rbs := p.k8s.RbacV1().RoleBindings(namespace)
154 _, err := rbs.Get(name, metav1.GetOptions{})
155 switch {
156 case err == nil:
157 // Already exists, update.
158 _, err = rbs.Update(rb)
159 return err
160 case errors.IsNotFound(err):
161 // Create.
162 _, err = rbs.Create(rb)
163 return err
164 default:
165 // Something went wrong.
166 return err
167 }
168}
169
170func (p *prodvider) ensureClusterRoleBindingGlobal(username string) error {
171 name := "sso:" + username + ":global"
172 rb := &rbacv1.ClusterRoleBinding{
173 ObjectMeta: metav1.ObjectMeta{
174 Name: name,
175 },
176 Subjects: []rbacv1.Subject{
177 {
178 APIGroup: "rbac.authorization.k8s.io",
179 Kind: "User",
180 Name: username + "@hackerspace.pl",
181 },
182 },
183 RoleRef: rbacv1.RoleRef{
184 APIGroup: "rbac.authorization.k8s.io",
185 Kind: "ClusterRole",
186 Name: "system:viewer",
187 },
188 }
189
190 crbs := p.k8s.RbacV1().ClusterRoleBindings()
191 _, err := crbs.Get(name, metav1.GetOptions{})
192 switch {
193 case err == nil:
194 // Already exists, update.
195 _, err = crbs.Update(rb)
196 return err
197 case errors.IsNotFound(err):
198 // Create.
199 _, err = crbs.Create(rb)
200 return err
201 default:
202 // Something went wrong.
203 return err
204 }
205}