Merge "Sample consumer to get kafka broker from ICS"
[nonrtric.git] / service-exposure / rapps-keycloak-mgr.go
1 // -
2 //
3 //      ========================LICENSE_START=================================
4 //      O-RAN-SC
5 //      %%
6 //      Copyright (C) 2022-2023: Nordix Foundation
7 //      %%
8 //      Licensed under the Apache License, Version 2.0 (the "License");
9 //      you may not use this file except in compliance with the License.
10 //      You may obtain a copy of the License at
11 //
12 //           http://www.apache.org/licenses/LICENSE-2.0
13 //
14 //      Unless required by applicable law or agreed to in writing, software
15 //      distributed under the License is distributed on an "AS IS" BASIS,
16 //      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 //      See the License for the specific language governing permissions and
18 //      limitations under the License.
19 //      ========================LICENSE_END===================================
20 package main
21
22 import (
23         "bytes"
24         "context"
25         "encoding/json"
26         "fmt"
27         "io/ioutil"
28         corev1 "k8s.io/api/core/v1"
29         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30         kubernetes "k8s.io/client-go/kubernetes"
31         "k8s.io/client-go/rest"
32         "net/http"
33         "net/url"
34         "rapps/utils/pemtojwks"
35 )
36
37 const (
38         namespace = "istio-nonrtric"
39 )
40
41 type Jwttoken struct {
42         Access_token       string
43         Expires_in         int
44         Refresh_expires_in int
45         Refresh_token      string
46         Token_type         string
47         Not_before_policy  int
48         Session_state      string
49         Scope              string
50 }
51
52 type RealmRepresentation struct {
53         Id          string `json:"id,omitempty"`
54         Realm       string `json:"realm,omitempty"`
55         DisplayName string `json:"displayName,omitempty"`
56         Enabled     bool   `json:"enabled"`
57 }
58
59 type Client struct {
60         ClientID                           string            `json:"clientId,omitempty"`
61         Enabled                            bool              `json:"enabled,omitempty"`
62         DirectAccessGrantsEnabled          bool              `json:"directAccessGrantsEnabled,omitempty"`
63         BearerOnly                         bool              `json:"bearerOnly,omitempty"`
64         PublicClient                       bool              `json:"publicClient,omitempty"`
65         ServiceAccountsEnabled             bool              `json:"serviceAccountsEnabled,omitempty"`
66         ClientAuthenticatorType            string            `json:"clientAuthenticatorType,omitempty"`
67         DefaultClientScopes                []string          `json:"defaultClientScopes,omitempty"`
68         Attributes                         map[string]string `json:"attributes,omitempty"`
69         AuthenticationFlowBindingOverrides map[string]string `json:"authenticationFlowBindingOverrides,omitempty"`
70 }
71
72 type Role struct {
73         Name string `json:"name,omitempty"`
74 }
75
76 type User struct {
77         ID       string `json:"id,omitempty"`
78         Username string `json:"username,omitempty"`
79         Email    string `json:"email,omitempty"`
80         Enabled  bool   `json:"enabled"`
81 }
82
83 type ProtocolMapperRepresentation struct {
84         Name           string            `json:"name,omitempty"`
85         Protocol       string            `json:"protocol,omitempty"`
86         ProtocolMapper string            `json:"protocolMapper,omitempty"`
87         Config         map[string]string `json:"config,omitempty"`
88 }
89
90 type RoleRepresentation struct {
91         ID         string `json:"id,omitempty"`
92         Name       string `json:"name,omitempty"`
93         Composite  bool   `json:"composite"`
94         ClientRole bool   `json:"clientRole"`
95 }
96
97 type AuthenticationFlowRepresentation struct {
98         Alias                   string   `json:"alias,omitempty"`
99         Description             string   `json:"description,omitempty"`
100         ProviderId              string   `json:"providerId,omitempty"`
101         TopLevel                bool     `json:"topLevel"`
102         BuiltIn                 bool     `json:"builtIn"`
103         AthenticationExecutions []string `json:"authenticationExecutions,omitempty"`
104 }
105
106 type Execution struct {
107         Provider string `json:"provider,omitempty"`
108 }
109
110 type AuthenticatorConfigRepresentation struct {
111         Alias  string            `json:"alias,omitempty"`
112         Config map[string]string `json:"config,omitempty"`
113 }
114
115 var keycloakUrl string = "http://keycloak:8080"
116 var token Jwttoken
117 var flowAlias string = "x509 direct grant"
118
119 func createClient(res http.ResponseWriter, req *http.Request) {
120         body, err := ioutil.ReadAll(req.Body)
121         if err != nil {
122                 panic(err.Error())
123         }
124         keyVal := make(map[string]string)
125         json.Unmarshal(body, &keyVal)
126         realmName := keyVal["realm"]
127         clientName := keyVal["name"]
128         role := keyVal["role"]
129         authType := keyVal["authType"]
130         tlsCrt := keyVal["tlsCrt"]
131         email := keyVal["email"]
132         subjectDN := keyVal["subjectDN"]
133         mappingSource := keyVal["mappingSource"]
134
135         var msg string
136         msg, err = create(realmName, clientName, role, authType, tlsCrt, email, subjectDN, mappingSource)
137         if err != nil {
138                 msg = err.Error()
139         }
140         if authType == "client-secret" {
141                 createSecret(msg, clientName, realmName, namespace)
142         }
143         // create response binary data
144         data := []byte(msg) // slice of bytes
145         // write `data` to response
146         res.Write(data)
147 }
148
149 func removeClient(res http.ResponseWriter, req *http.Request) {
150         query := req.URL.Query()
151         realmName := query.Get("realm")
152         clientName := query.Get("name")
153         authType := query.Get("authType")
154
155         var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
156         remove(realmName, clientName)
157         if authType == "client-secret" {
158                 removeSecret(namespace, clientName)
159         }
160         // create response binary data
161         data := []byte(msg) // slice of bytes
162         // write `data` to response
163         res.Write(data)
164 }
165
166 func main() {
167         createHandler := http.HandlerFunc(createClient)
168         http.Handle("/create", createHandler)
169         removeHandler := http.HandlerFunc(removeClient)
170         http.Handle("/remove", removeHandler)
171         http.ListenAndServe(":9000", nil)
172 }
173
174 func getAdminToken() {
175         var resp = &http.Response{}
176         var err error
177         username := "admin"
178         password := "admin"
179         clientId := "admin-cli"
180         restUrl := keycloakUrl + "/realms/master/protocol/openid-connect/token"
181         resp, err = http.PostForm(restUrl,
182                 url.Values{"username": {username}, "password": {password}, "grant_type": {"password"}, "client_id": {clientId}})
183         if err != nil {
184                 fmt.Println(err)
185                 panic("Something wrong with the credentials or url ")
186         }
187         defer resp.Body.Close()
188         body, err := ioutil.ReadAll(resp.Body)
189         json.Unmarshal([]byte(body), &token)
190 }
191
192 func sendRequest(method, url string, data []byte) (int, string) {
193         fmt.Printf("Sending %s request to %s\n", method, url)
194         req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
195         req.Header.Set("Content-Type", "application/json")
196         req.Header.Set("Authorization", "Bearer "+token.Access_token)
197
198         client := &http.Client{}
199         resp, err := client.Do(req)
200         if err != nil {
201                 panic(err)
202         }
203         defer resp.Body.Close()
204         body, _ := ioutil.ReadAll(resp.Body)
205         respString := string(body)
206         fmt.Println("response Status:", resp.Status)
207         return resp.StatusCode, respString
208 }
209
210 func create(realmName, clientName, clientRoleName, authType, tlsCrt, email, subjectDN, mappingSource string) (string, error) {
211         getAdminToken()
212         var userId string = ""
213         var jsonValue []byte = []byte{}
214         restUrl := keycloakUrl + "/realms/" + realmName
215         statusCode, _ := sendRequest("GET", restUrl, nil)
216
217         if statusCode != 200 {
218                 realmRepresentation := RealmRepresentation{
219                         Id:          realmName,
220                         Realm:       realmName,
221                         DisplayName: realmName,
222                         Enabled:     true,
223                 }
224                 restUrl := keycloakUrl + "/admin/realms"
225                 jsonValue, _ := json.Marshal(realmRepresentation)
226                 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
227         }
228
229         var flowId string = ""
230         if authType == "client-x509" {
231                 flowId = getFlowId(realmName)
232                 if flowId == "" {
233                         createx509Flow(realmName, mappingSource)
234                         flowId = getFlowId(realmName)
235                 }
236                 newUser := User{
237                         ID:       realmName + "user",
238                         Username: realmName + "user",
239                         Email:    email,
240                         Enabled:  true,
241                 }
242                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users"
243                 jsonValue, _ = json.Marshal(newUser)
244                 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
245                 userId = getUserId(realmName, realmName+"user")
246         }
247
248         newClient := getClient(authType, clientName, flowId, tlsCrt, subjectDN)
249         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients"
250         jsonValue, _ = json.Marshal(newClient)
251         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
252
253         clientId, clientSecret := getClientInfo(realmName, clientName)
254
255         newClientRole := Role{
256                 Name: clientRoleName,
257         }
258         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles"
259         jsonValue, _ = json.Marshal(newClientRole)
260         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
261
262         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles/" + clientRoleName
263         statusCode, data := sendRequest("GET", restUrl, nil)
264         roles := make(map[string]interface{})
265         err := json.Unmarshal([]byte(data), &roles)
266         if err != nil {
267                 fmt.Println(err)
268         }
269         roleId := fmt.Sprintf("%v", roles["id"])
270
271         if authType != "client-x509" {
272                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/service-account-user"
273                 statusCode, data = sendRequest("GET", restUrl, nil)
274                 serviceAccount := make(map[string]interface{})
275                 err = json.Unmarshal([]byte(data), &serviceAccount)
276                 if err != nil {
277                         fmt.Println(err)
278                 }
279                 userId = fmt.Sprintf("%v", serviceAccount["id"])
280         }
281
282         roleRepresentation := RoleRepresentation{
283                 ID:         roleId,
284                 Name:       clientRoleName,
285                 Composite:  false,
286                 ClientRole: true,
287         }
288
289         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId + "/role-mappings/clients/" + clientId
290         jsonValue, _ = json.Marshal([]RoleRepresentation{roleRepresentation})
291         statusCode, data = sendRequest("POST", restUrl, jsonValue)
292
293         clientroleMapper := ProtocolMapperRepresentation{
294                 Name:           "Client Role " + clientName + " Mapper",
295                 Protocol:       "openid-connect",
296                 ProtocolMapper: "oidc-usermodel-client-role-mapper",
297                 Config: map[string]string{
298                         "access.token.claim":                   "true",
299                         "aggregate.attrs":                      "",
300                         "claim.name":                           "clientRole",
301                         "id.token.claim":                       "true",
302                         "jsonType.label":                       "String",
303                         "multivalued":                          "true",
304                         "usermodel.clientRoleMapping.clientId": clientName,
305                         "userinfo.token.claim":                 "false",
306                 },
307         }
308
309         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/protocol-mappers/models"
310         jsonValue, _ = json.Marshal(clientroleMapper)
311         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
312         return clientSecret, nil
313 }
314
315 func getClient(authType, clientName, flowId, tlsCrt, subjectDN string) Client {
316         var newClient Client
317         newClient.ClientID = clientName
318         newClient.Enabled = true
319         newClient.DirectAccessGrantsEnabled = true
320         newClient.BearerOnly = false
321         newClient.PublicClient = false
322         newClient.ServiceAccountsEnabled = true
323         newClient.ClientAuthenticatorType = authType
324         newClient.DefaultClientScopes = []string{"email"}
325         if authType == "client-secret" {
326                 newClient.Attributes = map[string]string{
327                         "use.refresh.tokens":                   "true",
328                         "client_credentials.use_refresh_token": "true"}
329         } else if authType == "client-x509" {
330                 newClient.Attributes = map[string]string{
331                         "use.refresh.tokens":                   "true",
332                         "client_credentials.use_refresh_token": "true",
333                         "x509.subjectdn":                       ".*" + subjectDN + ".*",
334                         "x509.allow.regex.pattern.comparison":  "true"}
335                 newClient.AuthenticationFlowBindingOverrides = map[string]string{
336                         "direct_grant": flowId}
337         } else {
338                 jwksString, publicKey, kid := pemtojwks.CreateJWKS(tlsCrt)
339                 newClient.Attributes = map[string]string{
340                         "token.endpoint.auth.signing.alg":      "RS256",
341                         "jwt.credential.public.key":            publicKey,
342                         "jwt.credential.kid":                   kid,
343                         "use.jwks.url":                         "false",
344                         "jwks.url":                             jwksString,
345                         "use.refresh.tokens":                   "true",
346                         "client_credentials.use_refresh_token": "true",
347                 }
348         }
349         return newClient
350 }
351
352 func getClientInfo(realmName, clientName string) (string, string) {
353         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients?clientId=" + clientName
354         _, data := sendRequest("GET", restUrl, nil)
355
356         clients := make([]map[string]interface{}, 0)
357         err := json.Unmarshal([]byte(data), &clients)
358         if err != nil {
359                 fmt.Println(err)
360         }
361         clientId := fmt.Sprintf("%v", clients[0]["id"])
362         clientSecret := fmt.Sprintf("%v", clients[0]["secret"])
363         return clientId, clientSecret
364 }
365
366 func createx509Flow(realmName, mappingSource string) {
367         var jsonValue []byte = []byte{}
368         authenticationFlowRepresentation := AuthenticationFlowRepresentation{
369                 Alias:                   flowAlias,
370                 Description:             "OpenID Connect Resource Owner Grant",
371                 ProviderId:              "basic-flow",
372                 TopLevel:                true,
373                 BuiltIn:                 false,
374                 AthenticationExecutions: []string{},
375         }
376         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
377         jsonValue, _ = json.Marshal(authenticationFlowRepresentation)
378         sendRequest("POST", restUrl, jsonValue)
379
380         execution := Execution{
381                 Provider: "direct-grant-auth-x509-username",
382         }
383         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions/execution"
384         jsonValue, _ = json.Marshal(execution)
385         sendRequest("POST", restUrl, jsonValue)
386
387         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions"
388         _, data := sendRequest("GET", restUrl, nil)
389         executionInfo := make([]map[string]interface{}, 0)
390         err := json.Unmarshal([]byte(data), &executionInfo)
391         if err != nil {
392                 fmt.Println(err)
393         }
394         executionId := fmt.Sprintf("%v", executionInfo[0]["id"])
395
396         authenticatorConfigRepresentation := AuthenticatorConfigRepresentation{
397                 Alias: flowAlias + " config",
398                 Config: map[string]string{
399                         "x509-cert-auth.canonical-dn-enabled":           "false",
400                         "x509-cert-auth.serialnumber-hex-enabled":       "false",
401                         "x509-cert-auth.ocsp-fail-open":                 "false",
402                         "x509-cert-auth.regular-expression":             "(.*?)(?:$)",
403                         "x509-cert-auth.crl-checking-enabled":           "false",
404                         "x509-cert-auth.certificate-policy-mode":        "All",
405                         "x509-cert-auth.timestamp-validation-enabled":   "false",
406                         "x509-cert-auth.confirmation-page-disallowed":   "false",
407                         "x509-cert-auth.mapper-selection":               "Username or Email",
408                         "x509-cert-auth.revalidate-certificate-enabled": "false",
409                         "x509-cert-auth.crldp-checking-enabled":         "false",
410                         "x509-cert-auth.mapping-source-selection":       mappingSource,
411                         "x509-cert-auth.ocsp-checking-enabled":          "false",
412                 },
413         }
414         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/executions/" + executionId + "/config"
415         jsonValue, _ = json.Marshal(authenticatorConfigRepresentation)
416         sendRequest("POST", restUrl, jsonValue)
417 }
418
419 func getFlowId(realmName string) string {
420         var flowId string = ""
421         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
422         _, data := sendRequest("GET", restUrl, nil)
423         flows := make([]map[string]interface{}, 0)
424         err := json.Unmarshal([]byte(data), &flows)
425         if err != nil {
426                 fmt.Println(err)
427         }
428
429         for i, _ := range flows {
430                 id := fmt.Sprintf("%v", flows[i]["id"])
431                 alias := fmt.Sprintf("%v", flows[i]["alias"])
432                 if alias == flowAlias {
433                         flowId = id
434                 }
435         }
436         return flowId
437 }
438
439 func connectToK8s() *kubernetes.Clientset {
440         config, err := rest.InClusterConfig()
441         if err != nil {
442                 fmt.Println("failed to create K8s config")
443         }
444
445         clientset, err := kubernetes.NewForConfig(config)
446         if err != nil {
447                 fmt.Println("Failed to create K8s clientset")
448         }
449
450         return clientset
451 }
452
453 func createSecret(clientSecret, clientName, realmName, namespace string) {
454         secretName := clientName + "-secret"
455         clientset := connectToK8s()
456         secrets := clientset.CoreV1().Secrets(namespace)
457         secret := &corev1.Secret{
458                 ObjectMeta: metav1.ObjectMeta{
459                         Name:      secretName,
460                         Namespace: namespace,
461                         Labels: map[string]string{
462                                 "app": secretName,
463                         },
464                 },
465                 Type:       "Opaque",
466                 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
467         }
468
469         _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
470         if err != nil {
471                 fmt.Println("Failed to create K8s secret.", err)
472         }
473
474         fmt.Println("Created K8s secret successfully")
475 }
476
477 func remove(realmName, clientName string) {
478         getAdminToken()
479         clientId, _ := getClientInfo(realmName, clientName)
480
481         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId
482         sendRequest("DELETE", restUrl, nil)
483
484         var userId string = ""
485         userName := realmName + "user"
486         userId = getUserId(realmName, userName)
487         if userId != "" {
488                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId
489                 sendRequest("DELETE", restUrl, nil)
490         }
491
492         flowId := getFlowId(realmName)
493         if flowId != "" {
494                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowId
495                 sendRequest("DELETE", restUrl, nil)
496         }
497 }
498
499 func getUserId(realmName, userName string) string {
500         var userId string = ""
501         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/users?username=demouser"
502         _, data := sendRequest("GET", restUrl, nil)
503         user := make([]map[string]interface{}, 0)
504         err := json.Unmarshal([]byte(data), &user)
505         if err != nil {
506                 fmt.Println(err)
507         }
508         if len(user) > 0 {
509                 userId = fmt.Sprintf("%v", user[0]["id"])
510         }
511         return userId
512 }
513
514 func removeSecret(namespace, clientName string) {
515         clientset := connectToK8s()
516         secretName := clientName + "-secret"
517         secrets := clientset.CoreV1().Secrets(namespace)
518         err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{})
519         if err != nil {
520                 fmt.Println(err)
521         } else {
522                 fmt.Println("Deleted Secret", secretName)
523         }
524 }