blob: d7ad535ef1118c55da6bedaa48fbc7936c6cfecf [file] [log] [blame]
package main
import (
"context"
"encoding/pem"
"fmt"
"time"
"github.com/golang/glog"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
)
func (p *prodvider) kubernetesCreds(username string) (*pb.KubernetesKeys, error) {
o := fmt.Sprintf("sso:%s", username)
csrPEM, keyPEM, err := p.makeKubernetesCSR(username+"@hackerspace.pl", o)
if err != nil {
return nil, err
}
certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(13*time.Hour))
if err != nil {
return nil, err
}
caCert, _ := p.sign.Certificate("", "")
caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
// Build certificate chain from new cert and intermediate CA.
chainPEM := append(certPEM, caPEM...)
glog.Infof("Generated k8s certificate for %q", username)
return &pb.KubernetesKeys{
Cluster: "k0.hswaw.net",
// APIServerCA
Ca: p.kubeCAPEM,
// Chain of new cert + intermediate CA
Cert: chainPEM,
Key: keyPEM,
}, nil
}
func (p *prodvider) kubernetesConnect() error {
csrPEM, keyPEM, err := p.makeKubernetesCSR("prodvider", "system:masters")
if err != nil {
return err
}
certPEM, err := p.makeKubernetesCertificate(csrPEM, time.Now().Add(30*24*time.Hour))
if err != nil {
return err
}
caCert, _ := p.sign.Certificate("", "")
caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert.Raw})
glog.Infof("Generated k8s certificate for self (system:masters)")
// Build certificate chain from our cert and intermediate CA.
chainPEM := append(certPEM, caPEM...)
config := &rest.Config{
Host: flagKubernetesHost,
TLSClientConfig: rest.TLSClientConfig{
// Chain to authenticate ourselves (us + intermediate CA).
CertData: chainPEM,
KeyData: keyPEM,
// APIServer CA for verification.
CAData: p.kubeCAPEM,
},
}
cs, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
p.k8s = cs
return nil
}
// kubernetesSetupUser ensures that for a given SSO username we:
// - have a personal-<username> namespace
// - have a sso:<username>:personal rolebinding that binds
// system:admin-namespace to the user within their personal namespace
// - have a sso:<username>:global clusterrolebinding that binds
// system:viewer to the user at cluster level
func (p *prodvider) kubernetesSetupUser(ctx context.Context, username string) error {
namespace := "personal-" + username
if err := p.ensureNamespace(ctx, namespace); err != nil {
return err
}
if err := p.ensureRoleBindingPersonal(ctx, namespace, username); err != nil {
return err
}
if err := p.ensureClusterRoleBindingGlobal(ctx, username); err != nil {
return err
}
return nil
}
func (p *prodvider) ensureNamespace(ctx context.Context, name string) error {
_, err := p.k8s.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
switch {
case err == nil:
// Already exists, nothing to do
return nil
case errors.IsNotFound(err):
break
default:
// Something went wrong.
return err
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
_, err = p.k8s.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
return err
}
func (p *prodvider) ensureRoleBindingPersonal(ctx context.Context, namespace, username string) error {
name := "sso:" + username + ":personal"
rb := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: username + "@hackerspace.pl",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "system:admin-namespace",
},
}
rbs := p.k8s.RbacV1().RoleBindings(namespace)
_, err := rbs.Get(ctx, name, metav1.GetOptions{})
switch {
case err == nil:
// Already exists, update.
_, err = rbs.Update(ctx, rb, metav1.UpdateOptions{})
return err
case errors.IsNotFound(err):
// Create.
_, err = rbs.Create(ctx, rb, metav1.CreateOptions{})
return err
default:
// Something went wrong.
return err
}
}
func (p *prodvider) ensureClusterRoleBindingGlobal(ctx context.Context, username string) error {
name := "sso:" + username + ":global"
rb := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: username + "@hackerspace.pl",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "system:viewer",
},
}
crbs := p.k8s.RbacV1().ClusterRoleBindings()
_, err := crbs.Get(ctx, name, metav1.GetOptions{})
switch {
case err == nil:
// Already exists, update.
_, err = crbs.Update(ctx, rb, metav1.UpdateOptions{})
return err
case errors.IsNotFound(err):
// Create.
_, err = crbs.Create(ctx, rb, metav1.CreateOptions{})
return err
default:
// Something went wrong.
return err
}
}