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- namespace // - have a sso::personal rolebinding that binds // system:admin-namespace to the user within their personal namespace // - have a sso::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 } }