mirror of
https://github.com/ibuildthecloud/klum.git
synced 2026-02-14 14:39:50 +00:00
262 lines
6.3 KiB
Go
262 lines
6.3 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
klum "github.com/ibuildthecloud/klum/pkg/apis/klum.cattle.io/v1alpha1"
|
|
"github.com/ibuildthecloud/klum/pkg/generated/controllers/klum.cattle.io/v1alpha1"
|
|
v1controller "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
|
rbaccontroller "github.com/rancher/wrangler-api/pkg/generated/controllers/rbac/v1"
|
|
"github.com/rancher/wrangler/pkg/apply"
|
|
"github.com/rancher/wrangler/pkg/generic"
|
|
name2 "github.com/rancher/wrangler/pkg/name"
|
|
v1 "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/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
type Config struct {
|
|
Namespace string
|
|
ContextName string
|
|
Server string
|
|
CA string
|
|
DefaultClusterRole string
|
|
}
|
|
|
|
func Register(ctx context.Context,
|
|
cfg Config,
|
|
apply apply.Apply,
|
|
serviceAccount v1controller.ServiceAccountController,
|
|
crb rbaccontroller.ClusterRoleBindingController,
|
|
rb rbaccontroller.RoleBindingController,
|
|
secrets v1controller.SecretController,
|
|
kconfig v1alpha1.KubeconfigController,
|
|
user v1alpha1.UserController) {
|
|
|
|
h := &handler{
|
|
cfg: cfg,
|
|
apply: apply.WithCacheTypes(kconfig),
|
|
serviceAccounts: serviceAccount.Cache(),
|
|
}
|
|
|
|
v1alpha1.RegisterUserGeneratingHandler(ctx,
|
|
user,
|
|
apply.WithCacheTypes(serviceAccount,
|
|
crb, rb),
|
|
"",
|
|
"klum-user",
|
|
h.OnUserChange,
|
|
&generic.GeneratingHandlerOptions{
|
|
AllowClusterScoped: true,
|
|
})
|
|
|
|
secrets.OnChange(ctx, "klum-secret", h.OnSecretChange)
|
|
}
|
|
|
|
type handler struct {
|
|
cfg Config
|
|
apply apply.Apply
|
|
serviceAccounts v1controller.ServiceAccountCache
|
|
}
|
|
|
|
func (h *handler) OnUserChange(user *klum.User, status klum.UserStatus) ([]runtime.Object, klum.UserStatus, error) {
|
|
if user.Spec.Enabled != nil && !*user.Spec.Enabled {
|
|
status = setReady(status, false)
|
|
return nil, status, nil
|
|
}
|
|
|
|
objs := []runtime.Object{
|
|
&v1.ServiceAccount{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: user.Name,
|
|
Namespace: h.cfg.Namespace,
|
|
Annotations: map[string]string{
|
|
"klum.cattle.io/user": user.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
objs = append(objs, h.getRoles(user)...)
|
|
|
|
return objs, setReady(status, true), nil
|
|
}
|
|
|
|
func (h *handler) getRoles(user *klum.User) []runtime.Object {
|
|
subjects := []rbacv1.Subject{
|
|
{
|
|
Kind: "ServiceAccount",
|
|
Name: user.Name,
|
|
Namespace: h.cfg.Namespace,
|
|
},
|
|
}
|
|
|
|
if len(user.Spec.ClusterRoles) == 0 && len(user.Spec.Roles) == 0 {
|
|
if h.cfg.DefaultClusterRole == "" {
|
|
return nil
|
|
}
|
|
return []runtime.Object{
|
|
&rbacv1.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name(user.Name, "", h.cfg.DefaultClusterRole, ""),
|
|
},
|
|
Subjects: subjects,
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: "rbac.authorization.k8s.io",
|
|
Kind: "ClusterRole",
|
|
Name: h.cfg.DefaultClusterRole,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
var objs []runtime.Object
|
|
|
|
for _, clusterRole := range user.Spec.ClusterRoles {
|
|
objs = append(objs, &rbacv1.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name(user.Name, "", clusterRole, ""),
|
|
},
|
|
Subjects: subjects,
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: "rbac.authorization.k8s.io",
|
|
Kind: "ClusterRole",
|
|
Name: clusterRole,
|
|
},
|
|
})
|
|
}
|
|
|
|
for _, role := range user.Spec.Roles {
|
|
if role.Namespace == "" ||
|
|
role.Role == "" && role.ClusterRole == "" {
|
|
continue
|
|
}
|
|
|
|
if role.Role != "" {
|
|
rb := &rbacv1.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name(user.Name, role.Namespace, "", role.Role),
|
|
Namespace: role.Namespace,
|
|
},
|
|
Subjects: subjects,
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: "rbac.authorization.k8s.io",
|
|
Kind: "Role",
|
|
Name: role.Role,
|
|
},
|
|
}
|
|
objs = append(objs, rb)
|
|
}
|
|
|
|
if role.ClusterRole != "" {
|
|
rb := &rbacv1.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name(user.Name, role.Namespace, role.ClusterRole, ""),
|
|
Namespace: role.Namespace,
|
|
},
|
|
Subjects: subjects,
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: "rbac.authorization.k8s.io",
|
|
Kind: "ClusterRole",
|
|
Name: role.ClusterRole,
|
|
},
|
|
}
|
|
objs = append(objs, rb)
|
|
}
|
|
}
|
|
|
|
return objs
|
|
}
|
|
|
|
func name(user, namespace, clusterRole, role string) string {
|
|
// this is so we don't get conflicts
|
|
suffix := md5.Sum([]byte(fmt.Sprintf("%s/%s/%s/%s", user, namespace, clusterRole, role)))
|
|
if role == "" {
|
|
role = clusterRole
|
|
}
|
|
return name2.SafeConcatName("klum", user, role, hex.EncodeToString(suffix[:])[:8])
|
|
}
|
|
|
|
func (h *handler) OnSecretChange(key string, secret *v1.Secret) (*v1.Secret, error) {
|
|
if secret == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if secret.Type != v1.SecretTypeServiceAccountToken {
|
|
return secret, nil
|
|
}
|
|
|
|
sa, err := h.serviceAccounts.Get(secret.Namespace, secret.Annotations["kubernetes.io/service-account.name"])
|
|
if errors.IsNotFound(err) {
|
|
return secret, nil
|
|
} else if err != nil {
|
|
return secret, err
|
|
}
|
|
|
|
if sa.UID != types.UID(secret.Annotations["kubernetes.io/service-account.uid"]) {
|
|
return secret, nil
|
|
}
|
|
|
|
userName := sa.Annotations["klum.cattle.io/user"]
|
|
if userName == "" {
|
|
return secret, nil
|
|
}
|
|
|
|
ca := h.cfg.CA
|
|
if ca == "" {
|
|
ca = base64.StdEncoding.EncodeToString(secret.Data["ca.crt"])
|
|
}
|
|
token := string(secret.Data["token"])
|
|
|
|
return secret, h.apply.
|
|
WithOwner(secret).
|
|
WithSetOwnerReference(true, false).
|
|
ApplyObjects(&klum.Kubeconfig{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: userName,
|
|
},
|
|
Spec: klum.KubeconfigSpec{
|
|
Clusters: []klum.NamedCluster{
|
|
{
|
|
Name: h.cfg.ContextName,
|
|
Cluster: klum.Cluster{
|
|
Server: h.cfg.Server,
|
|
CertificateAuthorityData: ca,
|
|
},
|
|
},
|
|
},
|
|
AuthInfos: []klum.NamedAuthInfo{
|
|
{
|
|
Name: h.cfg.ContextName,
|
|
AuthInfo: klum.AuthInfo{
|
|
Token: token,
|
|
},
|
|
},
|
|
},
|
|
Contexts: []klum.NamedContext{
|
|
{
|
|
Name: h.cfg.ContextName,
|
|
Context: klum.Context{
|
|
Cluster: h.cfg.ContextName,
|
|
AuthInfo: h.cfg.ContextName,
|
|
},
|
|
},
|
|
},
|
|
CurrentContext: h.cfg.ContextName,
|
|
},
|
|
})
|
|
}
|
|
|
|
func setReady(status klum.UserStatus, ready bool) klum.UserStatus {
|
|
// dumb hack to set condition, should really make this easier
|
|
user := &klum.User{Status: status}
|
|
klum.UserReadyCondition.SetStatusBool(user, ready)
|
|
return user.Status
|
|
}
|