| // - |
| // ========================LICENSE_START================================= |
| // O-RAN-SC |
| // %% |
| // Copyright (C) 2022: Nordix Foundation |
| // %% |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ========================LICENSE_END=================================== |
| // |
| |
| package main |
| |
| import ( |
| "context" |
| "fmt" |
| "github.com/Nerzal/gocloak/v10" |
| corev1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| kubernetes "k8s.io/client-go/kubernetes" |
| "k8s.io/client-go/rest" |
| "net/http" |
| "strings" |
| "rapps/utils/pemtojwks" |
| ) |
| |
| const ( |
| namespace = "istio-nonrtric" |
| ) |
| |
| func createClient(res http.ResponseWriter, req *http.Request) { |
| query := req.URL.Query() |
| realmName := query.Get("realm") |
| clientName := query.Get("name") |
| role := query.Get("role") |
| var msg string |
| msg, err := create(realmName, clientName, role) |
| if err != nil { |
| msg = err.Error() |
| } |
| if realmName != "x509" && realmName != "jwt" { |
| createSecret(msg, clientName, realmName, role, namespace) |
| } |
| // create response binary data |
| data := []byte(msg) // slice of bytes |
| // write `data` to response |
| res.Write(data) |
| } |
| |
| func removeClient(res http.ResponseWriter, req *http.Request) { |
| query := req.URL.Query() |
| realmName := query.Get("realm") |
| clientName := query.Get("name") |
| role := query.Get("role") |
| |
| var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm" |
| remove(realmName, clientName) |
| if realmName != "x509" && realmName != "jwt" { |
| removeSecret(namespace, role) |
| } |
| // create response binary data |
| data := []byte(msg) // slice of bytes |
| // write `data` to response |
| res.Write(data) |
| } |
| |
| func main() { |
| createHandler := http.HandlerFunc(createClient) |
| http.Handle("/create", createHandler) |
| removeHandler := http.HandlerFunc(removeClient) |
| http.Handle("/remove", removeHandler) |
| http.ListenAndServe(":9000", nil) |
| } |
| |
| func create(realmName, clientName, clientRoleName string) (string, error) { |
| client := gocloak.NewClient("http://keycloak.default:8080") |
| ctx := context.Background() |
| token, err := client.LoginAdmin(ctx, "admin", "admin", "master") |
| if err != nil { |
| return "", err |
| } |
| |
| _, err = client.GetRealm(ctx, token.AccessToken, realmName) |
| if err != nil { |
| realmRepresentation := gocloak.RealmRepresentation{ |
| ID: gocloak.StringP(realmName), |
| Realm: gocloak.StringP(realmName), |
| DisplayName: gocloak.StringP(realmName), |
| Enabled: gocloak.BoolP(true), |
| } |
| |
| realm, err := client.CreateRealm(ctx, token.AccessToken, realmRepresentation) |
| if err != nil { |
| return "", err |
| } else { |
| fmt.Println("Created realm", realm) |
| } |
| } else { |
| fmt.Println("Realm already exists", realmName) |
| } |
| |
| flowAlias := "x509 direct grant" |
| flowId := "" |
| flows, err := client.GetAuthenticationFlows(ctx, token.AccessToken, realmName) |
| if err != nil { |
| fmt.Println("Oh no!, failed to get flows :(") |
| } else { |
| for _, flow := range flows { |
| if flow.Alias != nil && *flow.Alias == flowAlias { |
| flowId = *flow.ID |
| } |
| } |
| fmt.Println("Retrieved AuthenticationFlow id", flowId) |
| } |
| |
| newClient1 := gocloak.Client{ |
| ClientID: gocloak.StringP(clientName), |
| Enabled: gocloak.BoolP(true), |
| DirectAccessGrantsEnabled: gocloak.BoolP(true), |
| BearerOnly: gocloak.BoolP(false), |
| PublicClient: gocloak.BoolP(false), |
| ServiceAccountsEnabled: gocloak.BoolP(true), |
| ClientAuthenticatorType: gocloak.StringP("client-secret"), |
| DefaultClientScopes: &[]string{"email"}, |
| Attributes: &map[string]string{"use.refresh.tokens": "true", |
| "client_credentials.use_refresh_token": "true"}, |
| } |
| |
| newClient2 := gocloak.Client{ |
| ClientID: gocloak.StringP(clientName), |
| Enabled: gocloak.BoolP(true), |
| DirectAccessGrantsEnabled: gocloak.BoolP(true), |
| BearerOnly: gocloak.BoolP(false), |
| PublicClient: gocloak.BoolP(false), |
| ServiceAccountsEnabled: gocloak.BoolP(true), |
| ClientAuthenticatorType: gocloak.StringP("client-x509"), |
| DefaultClientScopes: &[]string{"openid", "profile", "email"}, |
| Attributes: &map[string]string{"use.refresh.tokens": "true", |
| "client_credentials.use_refresh_token": "true", |
| "x509.subjectdn": ".*client@mail.com.*", |
| "x509.allow.regex.pattern.comparison": "true"}, |
| AuthenticationFlowBindingOverrides: &map[string]string{"direct_grant": flowId}, |
| } |
| |
| jwksString := pemtojwks.CreateJWKS("/certs/client_pub.key", "public", "/certs/client.crt") |
| newClient3 := gocloak.Client{ |
| ClientID: gocloak.StringP(clientName), |
| Enabled: gocloak.BoolP(true), |
| DirectAccessGrantsEnabled: gocloak.BoolP(true), |
| BearerOnly: gocloak.BoolP(false), |
| PublicClient: gocloak.BoolP(false), |
| ServiceAccountsEnabled: gocloak.BoolP(true), |
| ClientAuthenticatorType: gocloak.StringP("client-jwt"), |
| DefaultClientScopes: &[]string{"email"}, |
| Attributes: &map[string]string{"token.endpoint.auth.signing.alg": "RS256", |
| "use.jwks.string": "true", |
| "jwks.string": jwksString, |
| "use.refresh.tokens": "true", |
| "client_credentials.use_refresh_token": "true", |
| }, |
| } |
| |
| var newClient gocloak.Client |
| if strings.HasPrefix(clientName, "x509") { |
| newClient = newClient2 |
| } else if strings.HasPrefix(clientName, "jwt") { |
| newClient = newClient3 |
| } else { |
| newClient = newClient1 |
| } |
| |
| clientId, err := client.CreateClient(ctx, token.AccessToken, realmName, newClient) |
| if err != nil { |
| fmt.Println("Failed to create client", err) |
| return "", err |
| } else { |
| fmt.Println("Created realm client", clientId) |
| } |
| |
| newClientRole := gocloak.Role{ |
| Name: gocloak.StringP(clientRoleName), |
| } |
| clientRoleName, err = client.CreateClientRole(ctx, token.AccessToken, realmName, clientId, newClientRole) |
| if err != nil { |
| return "", err |
| } else { |
| fmt.Println("Created client role", clientRoleName) |
| } |
| |
| user, err := client.GetClientServiceAccount(ctx, token.AccessToken, realmName, clientId) |
| if err != nil { |
| fmt.Println(err) |
| panic("Oh no!, failed to get client user :(") |
| } else { |
| fmt.Println("Service Account user", *user.Username) |
| } |
| |
| if strings.HasPrefix(clientName, "x509") { |
| newUser := gocloak.User{ |
| ID: gocloak.StringP(realmName + "user"), |
| Username: gocloak.StringP(realmName + "user"), |
| Email: gocloak.StringP("client@mail.com"), |
| Enabled: gocloak.BoolP(true), |
| } |
| |
| realmUser, err := client.CreateUser(ctx, token.AccessToken, realmName, newUser) |
| if err != nil { |
| fmt.Println(err) |
| panic("Oh no!, failed to create user :(") |
| } else { |
| fmt.Println("Created new user", realmUser) |
| } |
| } |
| |
| clientRole, err := client.GetClientRole(ctx, token.AccessToken, realmName, clientId, clientRoleName) |
| if err != nil { |
| fmt.Println(err) |
| panic("Oh no!, failed to get client role :(") |
| } else { |
| fmt.Println("Retrieved client role", clientRoleName) |
| } |
| |
| clientRoles := []gocloak.Role{*clientRole} |
| err = client.AddClientRoleToUser(ctx, token.AccessToken, realmName, clientId, *user.ID, clientRoles) |
| if err != nil { |
| fmt.Println(err) |
| panic("Oh no!, failed to add client role to user :(") |
| } else { |
| fmt.Printf("Added %s to %s\n", *clientRole.Name, *user.Username) |
| } |
| |
| clientroleMapper := gocloak.ProtocolMapperRepresentation{ |
| ID: gocloak.StringP("Client Role " + clientName + " Mapper"), |
| Name: gocloak.StringP("Client Role " + clientName + " Mapper"), |
| Protocol: gocloak.StringP("openid-connect"), |
| ProtocolMapper: gocloak.StringP("oidc-usermodel-client-role-mapper"), |
| Config: &map[string]string{ |
| "access.token.claim": "true", |
| "aggregate.attrs": "", |
| "claim.name": "clientRole", |
| "id.token.claim": "true", |
| "jsonType.label": "String", |
| "multivalued": "true", |
| "userinfo.token.claim": "true", |
| "usermodel.clientRoleMapping.clientId": clientName, |
| }, |
| } |
| _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper) |
| if err != nil { |
| fmt.Println(err) |
| panic("Oh no!, failed to add client roleampper to client :(") |
| } else { |
| fmt.Println("Client rolemapper added to client") |
| } |
| |
| if strings.HasPrefix(clientName, "x509") { |
| clientRole := *newClient.ClientID + "." + clientRoleName |
| |
| clientroleMapper := gocloak.ProtocolMapperRepresentation{ |
| ID: gocloak.StringP("Hardcoded " + clientName + " Mapper"), |
| Name: gocloak.StringP("Hardcoded " + clientName + " Mapper"), |
| Protocol: gocloak.StringP("openid-connect"), |
| ProtocolMapper: gocloak.StringP("oidc-hardcoded-role-mapper"), |
| Config: &map[string]string{ |
| "role": clientRole, |
| }, |
| } |
| _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper) |
| if err != nil { |
| return "", err |
| } else { |
| fmt.Println("Created hardcoded-role-mapper for ", clientRole) |
| } |
| } |
| |
| _, err = client.RegenerateClientSecret(ctx, token.AccessToken, realmName, clientId) |
| if err != nil { |
| return "", err |
| } |
| |
| cred, err := client.GetClientSecret(ctx, token.AccessToken, realmName, clientId) |
| if err != nil { |
| return "", err |
| } else { |
| fmt.Println("Generated client secret", *cred.Value) |
| } |
| |
| return *cred.Value, nil |
| } |
| |
| func connectToK8s() *kubernetes.Clientset { |
| config, err := rest.InClusterConfig() |
| if err != nil { |
| fmt.Println("failed to create K8s config") |
| } |
| |
| clientset, err := kubernetes.NewForConfig(config) |
| if err != nil { |
| fmt.Println("Failed to create K8s clientset") |
| } |
| |
| return clientset |
| } |
| |
| func createSecret(clientSecret, clientName, realmName, role, namespace string) { |
| secretName := role + "-secret" |
| clientset := connectToK8s() |
| secrets := clientset.CoreV1().Secrets(namespace) |
| secret := &corev1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: secretName, |
| Namespace: namespace, |
| Labels: map[string]string{ |
| "app": secretName, |
| }, |
| }, |
| Type: "Opaque", |
| StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName}, |
| } |
| |
| _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{}) |
| if err != nil { |
| fmt.Println("Failed to create K8s secret.", err) |
| } |
| |
| fmt.Println("Created K8s secret successfully") |
| } |
| |
| func remove(realmName, clientName string) { |
| adminClient := gocloak.NewClient("http://192.168.49.2:31560") |
| ctx := context.Background() |
| token, err := adminClient.LoginAdmin(ctx, "admin", "admin", "master") |
| if err != nil { |
| fmt.Println(err) |
| } |
| |
| clients, err := adminClient.GetClients(ctx, token.AccessToken, realmName, |
| gocloak.GetClientsParams{ |
| ClientID: gocloak.StringP(clientName), |
| }, |
| ) |
| if err != nil { |
| panic("List clients failed:" + err.Error()) |
| } |
| for _, client := range clients { |
| err = adminClient.DeleteClient(ctx, token.AccessToken, realmName, *client.ID) |
| if err != nil { |
| fmt.Println(err) |
| } else { |
| fmt.Println("Deleted client ", clientName) |
| } |
| } |
| |
| userName := realmName + "user" |
| users, err := adminClient.GetUsers(ctx, token.AccessToken, realmName, |
| gocloak.GetUsersParams{ |
| Username: gocloak.StringP(userName), |
| }) |
| if err != nil { |
| panic("List users failed:" + err.Error()) |
| } |
| for _, user := range users { |
| err = adminClient.DeleteUser(ctx, token.AccessToken, realmName, *user.ID) |
| if err != nil { |
| fmt.Println(err) |
| } else { |
| fmt.Println("Deleted user ", userName) |
| } |
| } |
| |
| } |
| |
| func removeSecret(namespace, role string) { |
| clientset := connectToK8s() |
| secretName := role + "-secret" |
| secrets := clientset.CoreV1().Secrets(namespace) |
| err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{}) |
| if err != nil { |
| fmt.Println(err) |
| } else { |
| fmt.Println("Deleted Secret", secretName) |
| } |
| } |