Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the option to create keycloak users for sandbox users #80

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
223 changes: 183 additions & 40 deletions internal/models/ocp_sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package models

import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand All @@ -20,6 +22,9 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)

type OcpSandboxProvider struct {
Expand Down Expand Up @@ -85,6 +90,16 @@ type TokenResponse struct {

var nameRegex = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)

// GenerateRandomPassword generates a random password of specified length.
func generateRandomPassword(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(bytes), nil
}


// Bind and Render
func (p *OcpSharedClusterConfiguration) Bind(r *http.Request) error {
// Ensure the name is not empty
Expand Down Expand Up @@ -921,6 +936,14 @@ func (a *OcpSandboxProvider) Request(serviceUuid string, cloud_selector map[stri
return
}

// Create an dynamic OpenShift client for non regular objects
dynclientset, err := dynamic.NewForConfig(config)
if err != nil {
log.Logger.Error("Error creating OCP client", "error", err)
rnew.SetStatus("error")
return
}

serviceAccountName := "sandbox"
suffix := annotations["namespace_suffix"]
if suffix == "" {
Expand Down Expand Up @@ -1025,6 +1048,96 @@ func (a *OcpSandboxProvider) Request(serviceUuid string, cloud_selector map[stri
rnew.SetStatus("error")
return
}
// Create an user if the keycloak option was enabled
if value, exists := cloud_selector["keycloak"]; exists && (value == "yes" || value == "true") {
// Generate a random password for the Keycloak user
userAccountName := "sandbox-" + guid
password, err := generateRandomPassword(16)
if err != nil {
log.Logger.Error("Error generating password", "error", err)
}

// Define the KeycloakUser GroupVersionResource
keycloakUserGVR := schema.GroupVersionResource{
Group: "keycloak.org",
Version: "v1alpha1",
Resource: "keycloakusers",
}

// Create the KeycloakUser object as an unstructured object
keycloakUser := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "keycloak.org/v1alpha1",
"kind": "KeycloakUser",
"metadata": map[string]interface{}{
"name": userAccountName,
"namespace": "rhsso", // The namespace where Keycloak is installed
},
"spec": map[string]interface{}{
"user": map[string]interface{}{
"username": userAccountName,
"enabled": true,
"credentials": []interface{}{
map[string]interface{}{
"type": "password",
"value": password,
"temporary": false,
},
},
},
"realmSelector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "sso", // The label selector for the Keycloak realm
},
},
},
},
}

// Create the KeycloakUser resource in the specified namespace
namespace := "rhsso"
_, err = dynclientset.Resource(keycloakUserGVR).Namespace(namespace).Create(context.TODO(), keycloakUser, metav1.CreateOptions{})
if err != nil {
log.Logger.Error("Error creating KeycloakUser", "error", err)
}

fmt.Println("KeycloakUser created successfully")
fridim marked this conversation as resolved.
Show resolved Hide resolved

}

// Assign ClusterRole sandbox-hcp (created with gitops) to the SA if hcp option was selected
if value, exists := cloud_selector["hcp"]; exists && (value == "yes" || value == "true") {
_, err = clientset.RbacV1().RoleBindings(namespaceName).Create(context.TODO(), &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName + "-hcp",
Labels: map[string]string{
"serviceUuid": serviceUuid,
"guid": annotations["guid"],
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: serviceAccountName + "-hcp",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: serviceAccountName,
Namespace: namespaceName,
},
},
}, metav1.CreateOptions{})

if err != nil {
log.Logger.Error("Error creating OCP RoleBind", "error", err)
if err := clientset.CoreV1().Namespaces().Delete(context.TODO(), namespaceName, metav1.DeleteOptions{}); err != nil {
log.Logger.Error("Error cleaning up the namespace", "error", err)
}
rnew.SetStatus("error")
return
}
}

// TODO: parameterize this, or detect when to execute it, otherwise it'll fail
// // Create RoleBind for the Service Account in the Namespace for kubevirt
Expand Down Expand Up @@ -1059,46 +1172,48 @@ func (a *OcpSandboxProvider) Request(serviceUuid string, cloud_selector map[stri
// return
// }

// Look if namespace 'cnv-images' exists
if _, err := clientset.CoreV1().Namespaces().Get(context.TODO(), "cnv-images", metav1.GetOptions{}); err == nil {

rb := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "allow-clone-" + namespaceName[:min(51, len(namespaceName))],
Namespace: "cnv-images",
Labels: map[string]string{
"serviceUuid": serviceUuid,
"guid": annotations["guid"],
},
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "default",
Namespace: namespaceName,
},
},
RoleRef: rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "datavolume-cloner",
APIGroup: "rbac.authorization.k8s.io",
},
}

_, err = clientset.RbacV1().RoleBindings("cnv-images").Create(context.TODO(), rb, metav1.CreateOptions{})
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
log.Logger.Error("Error creating rolebinding on cnv-images", "error", err)

if err := clientset.CoreV1().Namespaces().Delete(context.TODO(), namespaceName, metav1.DeleteOptions{}); err != nil {
log.Logger.Error("Error cleaning up the namespace", "error", err)
}
rnew.SetStatus("error")
return
}
}
}

// if cloud_selector has enabled the virt flag, then we give permission to cnv-images namespace
if value, exists := cloud_selector["virt"]; exists && (value == "yes" || value == "true") {
// Look if namespace 'cnv-images' exists
if _, err := clientset.CoreV1().Namespaces().Get(context.TODO(), "cnv-images", metav1.GetOptions{}); err == nil {

rb := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "allow-clone-" + namespaceName[:min(51, len(namespaceName))],
Namespace: "cnv-images",
Labels: map[string]string{
"serviceUuid": serviceUuid,
"guid": annotations["guid"],
},
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "default",
Namespace: namespaceName,
},
},
RoleRef: rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "datavolume-cloner",
APIGroup: "rbac.authorization.k8s.io",
},
}

_, err = clientset.RbacV1().RoleBindings("cnv-images").Create(context.TODO(), rb, metav1.CreateOptions{})
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
log.Logger.Error("Error creating rolebinding on cnv-images", "error", err)

if err := clientset.CoreV1().Namespaces().Delete(context.TODO(), namespaceName, metav1.DeleteOptions{}); err != nil {
log.Logger.Error("Error cleaning up the namespace", "error", err)
}
rnew.SetStatus("error")
return
}
}
}
}
secrets, err := clientset.CoreV1().Secrets(namespaceName).List(context.TODO(), metav1.ListOptions{})

if err != nil {
Expand Down Expand Up @@ -1406,6 +1521,16 @@ func (account *OcpSandboxWithCreds) Delete() error {
account.SetStatus("error")
return err
}

// Create an dynamic OpenShift client for non regular objects
dynclientset, err := dynamic.NewForConfig(config)
if err != nil {
log.Logger.Error("Error creating OCP client", "error", err, "name", account.Name)
account.SetStatus("error")
return err
}


// Define the Service Account name
serviceAccountName := "sandbox"

Expand Down Expand Up @@ -1460,6 +1585,24 @@ func (account *OcpSandboxWithCreds) Delete() error {
}
}

// Delete the User
userAccountName := "sandbox-" + account.Annotations["guid"]
fridim marked this conversation as resolved.
Show resolved Hide resolved
// Define the KeycloakUser GroupVersionResource
keycloakUserGVR := schema.GroupVersionResource{
Group: "keycloak.org",
Version: "v1alpha1",
Resource: "keycloakusers",
}

namespace := "rhsso"
err = dynclientset.Resource(keycloakUserGVR).Namespace(namespace).Delete(context.TODO(), userAccountName, metav1.DeleteOptions{})
if err != nil {
log.Logger.Error("Error deleting OCP namespace", "error", err, "name", account.Name)
fridim marked this conversation as resolved.
Show resolved Hide resolved
account.SetStatus("error")
return err
}


_, err = account.Provider.DbPool.Exec(
context.Background(),
"DELETE FROM resources WHERE id = $1",
Expand Down
Loading