forked from hswaw/hscloud
206 lines
5.1 KiB
Go
206 lines
5.1 KiB
Go
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
|
|
}
|
|
}
|