blob: 4f73ce470c99a95563c426035437818890d6cc74 [file] [log] [blame]
package main
import (
"context"
"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)
email := username + "@hackerspace.pl"
keyRaw, certBytes, err := p.makeKubernetesCertificate(email, o, time.Now().Add(13*time.Hour))
if err != nil {
return nil, err
}
// Build certificate chain from new cert and intermediate CA.
chainPEM := append(serializeCert(certBytes), serializeCert(p.intermediateCACert.Raw)...)
glog.Infof("Generated k8s certificate for %q", username)
return &pb.KubernetesKeys{
Cluster: "k0.hswaw.net",
// APIServerCA
Ca: serializeCert(p.kubeCACert.Raw),
// Chain of new cert + intermediate CA
Cert: chainPEM,
Key: serializeKey(keyRaw),
}, nil
}
func (p *prodvider) kubernetesConnect() error {
keyRaw, certBytes, err := p.makeKubernetesCertificate("prodvider", "system:masters", time.Now().Add(30*24*time.Hour))
if err != nil {
return err
}
glog.Infof("Generated k8s certificate for self (system:masters)")
// Build certificate chain from our cert and intermediate CA.
chainPEM := append(serializeCert(certBytes), serializeCert(p.intermediateCACert.Raw)...)
config := &rest.Config{
Host: flagKubernetesHost,
TLSClientConfig: rest.TLSClientConfig{
// Chain to authenticate ourselves (us + intermediate CA).
CertData: chainPEM,
KeyData: serializeKey(keyRaw),
// APIServer CA for verification.
CAData: serializeCert(p.kubeCACert.Raw),
},
}
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
}
}