2 // ========================LICENSE_START=================================
5 // Copyright (C) 2022: Nordix Foundation
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 // ========================LICENSE_END===================================
25 "github.com/Nerzal/gocloak/v10"
26 corev1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 kubernetes "k8s.io/client-go/kubernetes"
29 "k8s.io/client-go/rest"
31 "rapps/utils/pemtojwks"
35 namespace = "istio-nonrtric"
38 func createClient(res http.ResponseWriter, req *http.Request) {
39 query := req.URL.Query()
40 realmName := query.Get("realm")
41 clientName := query.Get("name")
42 role := query.Get("role")
43 authType := query.Get("authType")
45 msg, err := create(realmName, clientName, role, authType)
49 if authType == "client-secret" {
50 createSecret(msg, clientName, realmName, role, namespace)
52 // create response binary data
53 data := []byte(msg) // slice of bytes
54 // write `data` to response
58 func removeClient(res http.ResponseWriter, req *http.Request) {
59 query := req.URL.Query()
60 realmName := query.Get("realm")
61 clientName := query.Get("name")
62 role := query.Get("role")
63 authType := query.Get("authType")
65 var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
66 remove(realmName, clientName)
67 if authType == "client-secret" {
68 removeSecret(namespace, role)
70 // create response binary data
71 data := []byte(msg) // slice of bytes
72 // write `data` to response
77 createHandler := http.HandlerFunc(createClient)
78 http.Handle("/create", createHandler)
79 removeHandler := http.HandlerFunc(removeClient)
80 http.Handle("/remove", removeHandler)
81 http.ListenAndServe(":9000", nil)
84 func create(realmName, clientName, clientRoleName, authType string) (string, error) {
85 client := gocloak.NewClient("http://keycloak.default:8080")
86 ctx := context.Background()
87 token, err := client.LoginAdmin(ctx, "admin", "admin", "master")
92 _, err = client.GetRealm(ctx, token.AccessToken, realmName)
94 realmRepresentation := gocloak.RealmRepresentation{
95 ID: gocloak.StringP(realmName),
96 Realm: gocloak.StringP(realmName),
97 DisplayName: gocloak.StringP(realmName),
98 Enabled: gocloak.BoolP(true),
101 realm, err := client.CreateRealm(ctx, token.AccessToken, realmRepresentation)
105 fmt.Println("Created realm", realm)
108 fmt.Println("Realm already exists", realmName)
111 flowAlias := "x509 direct grant"
113 flows, err := client.GetAuthenticationFlows(ctx, token.AccessToken, realmName)
115 fmt.Println("Oh no!, failed to get flows :(")
117 for _, flow := range flows {
118 if flow.Alias != nil && *flow.Alias == flowAlias {
122 fmt.Println("Retrieved AuthenticationFlow id", flowId)
125 secretClient := gocloak.Client{
126 ClientID: gocloak.StringP(clientName),
127 Enabled: gocloak.BoolP(true),
128 DirectAccessGrantsEnabled: gocloak.BoolP(true),
129 BearerOnly: gocloak.BoolP(false),
130 PublicClient: gocloak.BoolP(false),
131 ServiceAccountsEnabled: gocloak.BoolP(true),
132 ClientAuthenticatorType: gocloak.StringP("client-secret"),
133 DefaultClientScopes: &[]string{"email"},
134 Attributes: &map[string]string{"use.refresh.tokens": "true",
135 "client_credentials.use_refresh_token": "true"},
138 x509Client := gocloak.Client{
139 ClientID: gocloak.StringP(clientName),
140 Enabled: gocloak.BoolP(true),
141 DirectAccessGrantsEnabled: gocloak.BoolP(true),
142 BearerOnly: gocloak.BoolP(false),
143 PublicClient: gocloak.BoolP(false),
144 ServiceAccountsEnabled: gocloak.BoolP(true),
145 ClientAuthenticatorType: gocloak.StringP("client-x509"),
146 DefaultClientScopes: &[]string{"openid", "profile", "email"},
147 Attributes: &map[string]string{"use.refresh.tokens": "true",
148 "client_credentials.use_refresh_token": "true",
149 "x509.subjectdn": ".*client@mail.com.*",
150 "x509.allow.regex.pattern.comparison": "true"},
151 AuthenticationFlowBindingOverrides: &map[string]string{"direct_grant": flowId},
154 jwksString := pemtojwks.CreateJWKS("/certs/client.crt")
155 jwtClient := gocloak.Client{
156 ClientID: gocloak.StringP(clientName),
157 Enabled: gocloak.BoolP(true),
158 DirectAccessGrantsEnabled: gocloak.BoolP(true),
159 BearerOnly: gocloak.BoolP(false),
160 PublicClient: gocloak.BoolP(false),
161 ServiceAccountsEnabled: gocloak.BoolP(true),
162 ClientAuthenticatorType: gocloak.StringP("client-jwt"),
163 DefaultClientScopes: &[]string{"email"},
164 Attributes: &map[string]string{"token.endpoint.auth.signing.alg": "RS256",
165 "use.jwks.string": "true",
166 "jwks.string": jwksString,
167 "use.refresh.tokens": "true",
168 "client_credentials.use_refresh_token": "true",
172 var newClient gocloak.Client
173 if authType == "client-x509" {
174 newClient = x509Client
175 } else if authType == "client-jwt" {
176 newClient = jwtClient
178 newClient = secretClient
181 clientId, err := client.CreateClient(ctx, token.AccessToken, realmName, newClient)
183 fmt.Println("Failed to create client", err)
186 fmt.Println("Created realm client", clientId)
189 newClientRole := gocloak.Role{
190 Name: gocloak.StringP(clientRoleName),
192 clientRoleName, err = client.CreateClientRole(ctx, token.AccessToken, realmName, clientId, newClientRole)
196 fmt.Println("Created client role", clientRoleName)
199 user, err := client.GetClientServiceAccount(ctx, token.AccessToken, realmName, clientId)
202 panic("Oh no!, failed to get client user :(")
204 fmt.Println("Service Account user", *user.Username)
207 if authType == "client-x509" {
208 newUser := gocloak.User{
209 ID: gocloak.StringP(realmName + "user"),
210 Username: gocloak.StringP(realmName + "user"),
211 Email: gocloak.StringP("client@mail.com"),
212 Enabled: gocloak.BoolP(true),
215 realmUser, err := client.CreateUser(ctx, token.AccessToken, realmName, newUser)
218 panic("Oh no!, failed to create user :(")
220 fmt.Println("Created new user", realmUser)
224 clientRole, err := client.GetClientRole(ctx, token.AccessToken, realmName, clientId, clientRoleName)
227 panic("Oh no!, failed to get client role :(")
229 fmt.Println("Retrieved client role", clientRoleName)
232 clientRoles := []gocloak.Role{*clientRole}
233 err = client.AddClientRoleToUser(ctx, token.AccessToken, realmName, clientId, *user.ID, clientRoles)
236 panic("Oh no!, failed to add client role to user :(")
238 fmt.Printf("Added %s to %s\n", *clientRole.Name, *user.Username)
241 clientroleMapper := gocloak.ProtocolMapperRepresentation{
242 ID: gocloak.StringP("Client Role " + clientName + " Mapper"),
243 Name: gocloak.StringP("Client Role " + clientName + " Mapper"),
244 Protocol: gocloak.StringP("openid-connect"),
245 ProtocolMapper: gocloak.StringP("oidc-usermodel-client-role-mapper"),
246 Config: &map[string]string{
247 "access.token.claim": "true",
248 "aggregate.attrs": "",
249 "claim.name": "clientRole",
250 "id.token.claim": "true",
251 "jsonType.label": "String",
252 "multivalued": "true",
253 "userinfo.token.claim": "true",
254 "usermodel.clientRoleMapping.clientId": clientName,
257 _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper)
260 panic("Oh no!, failed to add client roleampper to client :(")
262 fmt.Println("Client rolemapper added to client")
265 if authType == "client-x509" {
266 clientRole := *newClient.ClientID + "." + clientRoleName
268 clientroleMapper := gocloak.ProtocolMapperRepresentation{
269 ID: gocloak.StringP("Hardcoded " + clientName + " Mapper"),
270 Name: gocloak.StringP("Hardcoded " + clientName + " Mapper"),
271 Protocol: gocloak.StringP("openid-connect"),
272 ProtocolMapper: gocloak.StringP("oidc-hardcoded-role-mapper"),
273 Config: &map[string]string{
277 _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper)
281 fmt.Println("Created hardcoded-role-mapper for ", clientRole)
285 _, err = client.RegenerateClientSecret(ctx, token.AccessToken, realmName, clientId)
290 cred, err := client.GetClientSecret(ctx, token.AccessToken, realmName, clientId)
294 fmt.Println("Generated client secret", *cred.Value)
297 return *cred.Value, nil
300 func connectToK8s() *kubernetes.Clientset {
301 config, err := rest.InClusterConfig()
303 fmt.Println("failed to create K8s config")
306 clientset, err := kubernetes.NewForConfig(config)
308 fmt.Println("Failed to create K8s clientset")
314 func createSecret(clientSecret, clientName, realmName, role, namespace string) {
315 secretName := role + "-secret"
316 clientset := connectToK8s()
317 secrets := clientset.CoreV1().Secrets(namespace)
318 secret := &corev1.Secret{
319 ObjectMeta: metav1.ObjectMeta{
321 Namespace: namespace,
322 Labels: map[string]string{
327 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
330 _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
332 fmt.Println("Failed to create K8s secret.", err)
335 fmt.Println("Created K8s secret successfully")
338 func remove(realmName, clientName string) {
339 adminClient := gocloak.NewClient("http://keycloak.default:8080")
340 ctx := context.Background()
341 token, err := adminClient.LoginAdmin(ctx, "admin", "admin", "master")
346 clients, err := adminClient.GetClients(ctx, token.AccessToken, realmName,
347 gocloak.GetClientsParams{
348 ClientID: gocloak.StringP(clientName),
352 panic("List clients failed:" + err.Error())
354 for _, client := range clients {
355 err = adminClient.DeleteClient(ctx, token.AccessToken, realmName, *client.ID)
359 fmt.Println("Deleted client ", clientName)
363 userName := realmName + "user"
364 users, err := adminClient.GetUsers(ctx, token.AccessToken, realmName,
365 gocloak.GetUsersParams{
366 Username: gocloak.StringP(userName),
369 panic("List users failed:" + err.Error())
371 for _, user := range users {
372 err = adminClient.DeleteUser(ctx, token.AccessToken, realmName, *user.ID)
376 fmt.Println("Deleted user ", userName)
382 func removeSecret(namespace, role string) {
383 clientset := connectToK8s()
384 secretName := role + "-secret"
385 secrets := clientset.CoreV1().Secrets(namespace)
386 err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{})
390 fmt.Println("Deleted Secret", secretName)