Update keycloak version
[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         query := req.URL.Query()
121         realmName := query.Get("realm")
122         clientName := query.Get("name")
123         role := query.Get("role")
124         authType := query.Get("authType")
125         var msg string
126         msg, err := create(realmName, clientName, role, authType)
127         if err != nil {
128                 msg = err.Error()
129         }
130         if authType == "client-secret" {
131                 createSecret(msg, clientName, realmName, namespace)
132         }
133         // create response binary data
134         data := []byte(msg) // slice of bytes
135         // write `data` to response
136         res.Write(data)
137 }
138
139 func removeClient(res http.ResponseWriter, req *http.Request) {
140         query := req.URL.Query()
141         realmName := query.Get("realm")
142         clientName := query.Get("name")
143         authType := query.Get("authType")
144
145         var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
146         remove(realmName, clientName)
147         if authType == "client-secret" {
148                 removeSecret(namespace, clientName)
149         }
150         // create response binary data
151         data := []byte(msg) // slice of bytes
152         // write `data` to response
153         res.Write(data)
154 }
155
156 func main() {
157         createHandler := http.HandlerFunc(createClient)
158         http.Handle("/create", createHandler)
159         removeHandler := http.HandlerFunc(removeClient)
160         http.Handle("/remove", removeHandler)
161         http.ListenAndServe(":9000", nil)
162 }
163
164 func getAdminToken() {
165         var resp = &http.Response{}
166         var err error
167         username := "admin"
168         password := "admin"
169         clientId := "admin-cli"
170         restUrl := keycloakUrl + "/realms/master/protocol/openid-connect/token"
171         resp, err = http.PostForm(restUrl,
172                 url.Values{"username": {username}, "password": {password}, "grant_type": {"password"}, "client_id": {clientId}})
173         if err != nil {
174                 fmt.Println(err)
175                 panic("Something wrong with the credentials or url ")
176         }
177         defer resp.Body.Close()
178         body, err := ioutil.ReadAll(resp.Body)
179         json.Unmarshal([]byte(body), &token)
180 }
181
182 func sendRequest(method, url string, data []byte) (int, string) {
183         fmt.Printf("Sending %s request to %s\n", method, url)
184         req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
185         req.Header.Set("Content-Type", "application/json")
186         req.Header.Set("Authorization", "Bearer "+token.Access_token)
187
188         client := &http.Client{}
189         resp, err := client.Do(req)
190         if err != nil {
191                 panic(err)
192         }
193         defer resp.Body.Close()
194         body, _ := ioutil.ReadAll(resp.Body)
195         respString := string(body)
196         fmt.Println("response Status:", resp.Status)
197         return resp.StatusCode, respString
198 }
199
200 func create(realmName, clientName, clientRoleName, authType string) (string, error) {
201         getAdminToken()
202         var userId string = ""
203         var jsonValue []byte = []byte{}
204         restUrl := keycloakUrl + "/realms/" + realmName
205         statusCode, _ := sendRequest("GET", restUrl, nil)
206
207         if statusCode != 200 {
208                 realmRepresentation := RealmRepresentation{
209                         Id:          realmName,
210                         Realm:       realmName,
211                         DisplayName: realmName,
212                         Enabled:     true,
213                 }
214                 restUrl := keycloakUrl + "/admin/realms"
215                 jsonValue, _ := json.Marshal(realmRepresentation)
216                 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
217         }
218
219         var flowId string = ""
220         if authType == "client-x509" {
221                 flowId = getFlowId(realmName)
222                 if flowId == "" {
223                         createx509Flow(realmName)
224                         flowId = getFlowId(realmName)
225                 }
226                 newUser := User{
227                         ID:       realmName + "user",
228                         Username: realmName + "user",
229                         Email:    "client@mail.com",
230                         Enabled:  true,
231                 }
232                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users"
233                 jsonValue, _ = json.Marshal(newUser)
234                 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
235                 userId = getUserId(realmName, realmName+"user")
236         }
237
238         newClient := getClient(authType, clientName, flowId)
239         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients"
240         jsonValue, _ = json.Marshal(newClient)
241         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
242
243         clientId, clientSecret := getClientInfo(realmName, clientName)
244
245         newClientRole := Role{
246                 Name: clientRoleName,
247         }
248         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles"
249         jsonValue, _ = json.Marshal(newClientRole)
250         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
251
252         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles/" + clientRoleName
253         statusCode, data := sendRequest("GET", restUrl, nil)
254         roles := make(map[string]interface{})
255         err := json.Unmarshal([]byte(data), &roles)
256         if err != nil {
257                 fmt.Println(err)
258         }
259         roleId := fmt.Sprintf("%v", roles["id"])
260
261         if authType != "client-x509" {
262                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/service-account-user"
263                 statusCode, data = sendRequest("GET", restUrl, nil)
264                 serviceAccount := make(map[string]interface{})
265                 err = json.Unmarshal([]byte(data), &serviceAccount)
266                 if err != nil {
267                         fmt.Println(err)
268                 }
269                 userId = fmt.Sprintf("%v", serviceAccount["id"])
270         }
271
272         roleRepresentation := RoleRepresentation{
273                 ID:         roleId,
274                 Name:       clientRoleName,
275                 Composite:  false,
276                 ClientRole: true,
277         }
278
279         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId + "/role-mappings/clients/" + clientId
280         jsonValue, _ = json.Marshal([]RoleRepresentation{roleRepresentation})
281         statusCode, data = sendRequest("POST", restUrl, jsonValue)
282
283         clientroleMapper := ProtocolMapperRepresentation{
284                 Name:           "Client Role " + clientName + " Mapper",
285                 Protocol:       "openid-connect",
286                 ProtocolMapper: "oidc-usermodel-client-role-mapper",
287                 Config: map[string]string{
288                         "access.token.claim":                   "true",
289                         "aggregate.attrs":                      "",
290                         "claim.name":                           "clientRole",
291                         "id.token.claim":                       "true",
292                         "jsonType.label":                       "String",
293                         "multivalued":                          "true",
294                         "usermodel.clientRoleMapping.clientId": clientName,
295                         "userinfo.token.claim":                 "false",
296                 },
297         }
298
299         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/protocol-mappers/models"
300         jsonValue, _ = json.Marshal(clientroleMapper)
301         statusCode, _ = sendRequest("POST", restUrl, jsonValue)
302         return clientSecret, nil
303 }
304
305 func getClient(authType, clientName, flowId string) Client {
306         var newClient Client
307         newClient.ClientID = clientName
308         newClient.Enabled = true
309         newClient.DirectAccessGrantsEnabled = true
310         newClient.BearerOnly = false
311         newClient.PublicClient = false
312         newClient.ServiceAccountsEnabled = true
313         newClient.ClientAuthenticatorType = authType
314         newClient.DefaultClientScopes = []string{"email"}
315         if authType == "client-secret" {
316                 newClient.Attributes = map[string]string{
317                         "use.refresh.tokens":                   "true",
318                         "client_credentials.use_refresh_token": "true"}
319         } else if authType == "client-x509" {
320                 newClient.Attributes = map[string]string{
321                         "use.refresh.tokens":                   "true",
322                         "client_credentials.use_refresh_token": "true",
323                         "x509.subjectdn":                       ".*client@mail.com.*",
324                         "x509.allow.regex.pattern.comparison":  "true"}
325                 newClient.AuthenticationFlowBindingOverrides = map[string]string{
326                         "direct_grant": flowId}
327         } else {
328                 jwksString, publicKey, kid := pemtojwks.CreateJWKS("/certs/client.crt")
329                 newClient.Attributes = map[string]string{
330                         "token.endpoint.auth.signing.alg":      "RS256",
331                         "jwt.credential.public.key":            publicKey,
332                         "jwt.credential.kid":                   kid,
333                         "use.jwks.url":                         "false",
334                         "jwks.url":                             jwksString,
335                         "use.refresh.tokens":                   "true",
336                         "client_credentials.use_refresh_token": "true",
337                 }
338         }
339         return newClient
340 }
341
342 func getClientInfo(realmName, clientName string) (string, string) {
343         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients?clientId=" + clientName
344         _, data := sendRequest("GET", restUrl, nil)
345
346         clients := make([]map[string]interface{}, 0)
347         err := json.Unmarshal([]byte(data), &clients)
348         if err != nil {
349                 fmt.Println(err)
350         }
351         clientId := fmt.Sprintf("%v", clients[0]["id"])
352         clientSecret := fmt.Sprintf("%v", clients[0]["secret"])
353         return clientId, clientSecret
354 }
355
356 func createx509Flow(realmName string) {
357         var jsonValue []byte = []byte{}
358         authenticationFlowRepresentation := AuthenticationFlowRepresentation{
359                 Alias:                   flowAlias,
360                 Description:             "OpenID Connect Resource Owner Grant",
361                 ProviderId:              "basic-flow",
362                 TopLevel:                true,
363                 BuiltIn:                 false,
364                 AthenticationExecutions: []string{},
365         }
366         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
367         jsonValue, _ = json.Marshal(authenticationFlowRepresentation)
368         sendRequest("POST", restUrl, jsonValue)
369
370         execution := Execution{
371                 Provider: "direct-grant-auth-x509-username",
372         }
373         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions/execution"
374         jsonValue, _ = json.Marshal(execution)
375         sendRequest("POST", restUrl, jsonValue)
376
377         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions"
378         _, data := sendRequest("GET", restUrl, nil)
379         executionInfo := make([]map[string]interface{}, 0)
380         err := json.Unmarshal([]byte(data), &executionInfo)
381         if err != nil {
382                 fmt.Println(err)
383         }
384         executionId := fmt.Sprintf("%v", executionInfo[0]["id"])
385
386         authenticatorConfigRepresentation := AuthenticatorConfigRepresentation{
387                 Alias: flowAlias + " config",
388                 Config: map[string]string{
389                         "x509-cert-auth.canonical-dn-enabled":           "false",
390                         "x509-cert-auth.serialnumber-hex-enabled":       "false",
391                         "x509-cert-auth.ocsp-fail-open":                 "false",
392                         "x509-cert-auth.regular-expression":             "(.*?)(?:$)",
393                         "x509-cert-auth.crl-checking-enabled":           "false",
394                         "x509-cert-auth.certificate-policy-mode":        "All",
395                         "x509-cert-auth.timestamp-validation-enabled":   "false",
396                         "x509-cert-auth.confirmation-page-disallowed":   "false",
397                         "x509-cert-auth.mapper-selection":               "Username or Email",
398                         "x509-cert-auth.revalidate-certificate-enabled": "false",
399                         "x509-cert-auth.crldp-checking-enabled":         "false",
400                         "x509-cert-auth.mapping-source-selection":       "Subject's e-mail",
401                         "x509-cert-auth.ocsp-checking-enabled":          "false",
402                 },
403         }
404         restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/executions/" + executionId + "/config"
405         jsonValue, _ = json.Marshal(authenticatorConfigRepresentation)
406         sendRequest("POST", restUrl, jsonValue)
407 }
408
409 func getFlowId(realmName string) string {
410         var flowId string = ""
411         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
412         _, data := sendRequest("GET", restUrl, nil)
413         flows := make([]map[string]interface{}, 0)
414         err := json.Unmarshal([]byte(data), &flows)
415         if err != nil {
416                 fmt.Println(err)
417         }
418
419         for i, _ := range flows {
420                 id := fmt.Sprintf("%v", flows[i]["id"])
421                 alias := fmt.Sprintf("%v", flows[i]["alias"])
422                 if alias == flowAlias {
423                         flowId = id
424                 }
425         }
426         return flowId
427 }
428
429 func connectToK8s() *kubernetes.Clientset {
430         config, err := rest.InClusterConfig()
431         if err != nil {
432                 fmt.Println("failed to create K8s config")
433         }
434
435         clientset, err := kubernetes.NewForConfig(config)
436         if err != nil {
437                 fmt.Println("Failed to create K8s clientset")
438         }
439
440         return clientset
441 }
442
443 func createSecret(clientSecret, clientName, realmName, namespace string) {
444         secretName := clientName + "-secret"
445         clientset := connectToK8s()
446         secrets := clientset.CoreV1().Secrets(namespace)
447         secret := &corev1.Secret{
448                 ObjectMeta: metav1.ObjectMeta{
449                         Name:      secretName,
450                         Namespace: namespace,
451                         Labels: map[string]string{
452                                 "app": secretName,
453                         },
454                 },
455                 Type:       "Opaque",
456                 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
457         }
458
459         _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
460         if err != nil {
461                 fmt.Println("Failed to create K8s secret.", err)
462         }
463
464         fmt.Println("Created K8s secret successfully")
465 }
466
467 func remove(realmName, clientName string) {
468         getAdminToken()
469         clientId, _ := getClientInfo(realmName, clientName)
470
471         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId
472         sendRequest("DELETE", restUrl, nil)
473
474         var userId string = ""
475         userName := realmName + "user"
476         userId = getUserId(realmName, userName)
477         if userId != "" {
478                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId
479                 sendRequest("DELETE", restUrl, nil)
480         }
481
482         flowId := getFlowId(realmName)
483         if flowId != "" {
484                 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowId
485                 sendRequest("DELETE", restUrl, nil)
486         }
487 }
488
489 func getUserId(realmName, userName string) string {
490         var userId string = ""
491         restUrl := keycloakUrl + "/admin/realms/" + realmName + "/users?username=demouser"
492         _, data := sendRequest("GET", restUrl, nil)
493         user := make([]map[string]interface{}, 0)
494         err := json.Unmarshal([]byte(data), &user)
495         if err != nil {
496                 fmt.Println(err)
497         }
498         if len(user) > 0 {
499                 userId = fmt.Sprintf("%v", user[0]["id"])
500         }
501         return userId
502 }
503
504 func removeSecret(namespace, clientName string) {
505         clientset := connectToK8s()
506         secretName := clientName + "-secret"
507         secrets := clientset.CoreV1().Secrets(namespace)
508         err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{})
509         if err != nil {
510                 fmt.Println(err)
511         } else {
512                 fmt.Println("Deleted Secret", secretName)
513         }
514 }