239 lines
6.5 KiB
Go
239 lines
6.5 KiB
Go
package iam
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apiserver/pkg/registry/generic/registry"
|
|
|
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
|
v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
|
)
|
|
|
|
// AfterUserCreate is a post-create hook that writes the user's basic role assignment to Zanzana (openFGA)
|
|
func (b *IdentityAccessManagementAPIBuilder) AfterUserCreate(obj runtime.Object, _ *metav1.CreateOptions) {
|
|
if b.zClient == nil {
|
|
return
|
|
}
|
|
|
|
user, ok := obj.(*iamv0.User)
|
|
if !ok {
|
|
b.logger.Error("failed to convert object to User type", "object", obj)
|
|
return
|
|
}
|
|
|
|
// Skip if user has no role assigned
|
|
if user.Spec.Role == "" {
|
|
b.logger.Debug("user has no role assigned, skipping basic role sync",
|
|
"namespace", user.Namespace,
|
|
"name", user.Name,
|
|
)
|
|
return
|
|
}
|
|
|
|
resourceType := "user"
|
|
operation := "create"
|
|
|
|
// Grab a ticket to write to Zanzana
|
|
wait := time.Now()
|
|
b.zTickets <- true
|
|
hooksWaitHistogram.WithLabelValues(resourceType, operation).Observe(time.Since(wait).Seconds())
|
|
|
|
go func(namespace, subjectName, role, resourceType, operation string) {
|
|
start := time.Now()
|
|
status := "success"
|
|
|
|
defer func() {
|
|
<-b.zTickets
|
|
hooksDurationHistogram.WithLabelValues(resourceType, operation, status).Observe(time.Since(start).Seconds())
|
|
hooksOperationCounter.WithLabelValues(resourceType, operation, status).Inc()
|
|
}()
|
|
|
|
b.logger.Debug("writing user basic role to zanzana",
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"role", role,
|
|
)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
|
|
defer cancel()
|
|
|
|
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
|
|
Namespace: namespace,
|
|
Operations: []*v1.MutateOperation{
|
|
{
|
|
Operation: &v1.MutateOperation_UpdateUserOrgRole{
|
|
UpdateUserOrgRole: &v1.UpdateUserOrgRoleOperation{User: subjectName, Role: role},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
status = "failure"
|
|
b.logger.Error("failed to write user basic role to zanzana",
|
|
"err", err,
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"role", role,
|
|
)
|
|
} else {
|
|
hooksTuplesCounter.WithLabelValues(resourceType, operation, "write").Inc()
|
|
}
|
|
}(user.Namespace, user.Name, user.Spec.Role, resourceType, operation)
|
|
}
|
|
|
|
// BeginUserUpdate is a pre-update hook that gets called on user updates
|
|
// It compares old and new roles and performs the zanzana write after K8s update succeeds
|
|
func (b *IdentityAccessManagementAPIBuilder) BeginUserUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (registry.FinishFunc, error) {
|
|
if b.zClient == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
oldUser, ok := oldObj.(*iamv0.User)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
newUser, ok := obj.(*iamv0.User)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
// If role hasn't changed, no need to update
|
|
if oldUser.Spec.Role == newUser.Spec.Role {
|
|
return nil, nil
|
|
}
|
|
|
|
// Return a finish function that performs the zanzana write only on success
|
|
return func(ctx context.Context, success bool) {
|
|
if !success {
|
|
return
|
|
}
|
|
|
|
wait := time.Now()
|
|
b.zTickets <- true
|
|
hooksWaitHistogram.WithLabelValues("user", "update").Observe(time.Since(wait).Seconds())
|
|
|
|
go func(namespace, subjectName, oldRole, newRole string) {
|
|
start := time.Now()
|
|
status := "success"
|
|
|
|
defer func() {
|
|
<-b.zTickets
|
|
hooksDurationHistogram.WithLabelValues("user", "update", status).Observe(time.Since(start).Seconds())
|
|
hooksOperationCounter.WithLabelValues("user", "update", status).Inc()
|
|
}()
|
|
|
|
b.logger.Debug("updating user basic role in zanzana",
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"oldRole", oldRole,
|
|
"newRole", newRole,
|
|
)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
|
|
defer cancel()
|
|
|
|
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
|
|
Namespace: namespace,
|
|
Operations: []*v1.MutateOperation{
|
|
{
|
|
Operation: &v1.MutateOperation_UpdateUserOrgRole{
|
|
UpdateUserOrgRole: &v1.UpdateUserOrgRoleOperation{User: subjectName, Role: newRole},
|
|
},
|
|
}, {
|
|
Operation: &v1.MutateOperation_DeleteUserOrgRole{
|
|
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{User: subjectName, Role: oldRole},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
status = "failure"
|
|
b.logger.Error("failed to update user basic role in zanzana",
|
|
"err", err,
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"role", newRole,
|
|
"oldRole", oldRole,
|
|
)
|
|
}
|
|
}(oldUser.Namespace, oldUser.Name, oldUser.Spec.Role, newUser.Spec.Role)
|
|
}, nil
|
|
}
|
|
|
|
// AfterUserDelete is a post-delete hook that removes the user's basic role assignment from Zanzana (openFGA)
|
|
func (b *IdentityAccessManagementAPIBuilder) AfterUserDelete(obj runtime.Object, _ *metav1.DeleteOptions) {
|
|
if b.zClient == nil {
|
|
return
|
|
}
|
|
|
|
user, ok := obj.(*iamv0.User)
|
|
if !ok {
|
|
b.logger.Error("failed to convert object to User type", "object", obj)
|
|
return
|
|
}
|
|
|
|
resourceType := "user"
|
|
operation := "delete"
|
|
|
|
// Skip if user had no role assigned
|
|
if user.Spec.Role == "" {
|
|
b.logger.Debug("user had no role assigned, skipping basic role sync",
|
|
"namespace", user.Namespace,
|
|
"name", user.Name,
|
|
)
|
|
return
|
|
}
|
|
|
|
wait := time.Now()
|
|
b.zTickets <- true
|
|
hooksWaitHistogram.WithLabelValues(resourceType, operation).Observe(time.Since(wait).Seconds())
|
|
|
|
go func(namespace, subjectName, role string) {
|
|
start := time.Now()
|
|
status := "success"
|
|
|
|
defer func() {
|
|
<-b.zTickets
|
|
hooksDurationHistogram.WithLabelValues(resourceType, operation, status).Observe(time.Since(start).Seconds())
|
|
hooksOperationCounter.WithLabelValues(resourceType, operation, status).Inc()
|
|
}()
|
|
|
|
b.logger.Debug("deleting user basic role from zanzana",
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"role", role,
|
|
)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
|
|
defer cancel()
|
|
|
|
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
|
|
Namespace: namespace,
|
|
Operations: []*v1.MutateOperation{
|
|
{
|
|
Operation: &v1.MutateOperation_DeleteUserOrgRole{
|
|
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{User: subjectName, Role: role},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
status = "failure"
|
|
b.logger.Error("failed to delete user basic role from zanzana",
|
|
"err", err,
|
|
"namespace", namespace,
|
|
"name", subjectName,
|
|
"role", role,
|
|
)
|
|
} else {
|
|
hooksTuplesCounter.WithLabelValues(resourceType, operation, "delete").Inc()
|
|
}
|
|
}(user.Namespace, user.Name, user.Spec.Role)
|
|
}
|