First version of service exposure
[nonrtric.git] / service-exposure / rapps-keycloak-mgr.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
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
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
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===================================
19 //
20
21 package main
22
23 import (
24         "context"
25         "fmt"
26         "github.com/Nerzal/gocloak/v10"
27         corev1 "k8s.io/api/core/v1"
28         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29         kubernetes "k8s.io/client-go/kubernetes"
30         "k8s.io/client-go/rest"
31         "net/http"
32         "strings"
33         "rapps/utils/pemtojwks"
34 )
35
36 const (
37         namespace = "istio-nonrtric"
38 )
39
40 func createClient(res http.ResponseWriter, req *http.Request) {
41         query := req.URL.Query()
42         realmName := query.Get("realm")
43         clientName := query.Get("name")
44         role := query.Get("role")
45         var msg string
46         msg, err := create(realmName, clientName, role)
47         if err != nil {
48                 msg = err.Error()
49         }
50         if realmName != "x509" && realmName != "jwt" {
51                 createSecret(msg, clientName, realmName, role, namespace)
52         }
53         // create response binary data
54         data := []byte(msg) // slice of bytes
55         // write `data` to response
56         res.Write(data)
57 }
58
59 func removeClient(res http.ResponseWriter, req *http.Request) {
60         query := req.URL.Query()
61         realmName := query.Get("realm")
62         clientName := query.Get("name")
63         role := query.Get("role")
64
65         var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
66         remove(realmName, clientName)
67         if realmName != "x509" && realmName != "jwt" {
68                 removeSecret(namespace, role)
69         }
70         // create response binary data
71         data := []byte(msg) // slice of bytes
72         // write `data` to response
73         res.Write(data)
74 }
75
76 func main() {
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)
82 }
83
84 func create(realmName, clientName, clientRoleName string) (string, error) {
85         client := gocloak.NewClient("http://keycloak.default:8080")
86         ctx := context.Background()
87         token, err := client.LoginAdmin(ctx, "admin", "admin", "master")
88         if err != nil {
89                 return "", err
90         }
91
92         _, err = client.GetRealm(ctx, token.AccessToken, realmName)
93         if err != nil {
94                 realmRepresentation := gocloak.RealmRepresentation{
95                         ID:          gocloak.StringP(realmName),
96                         Realm:       gocloak.StringP(realmName),
97                         DisplayName: gocloak.StringP(realmName),
98                         Enabled:     gocloak.BoolP(true),
99                 }
100
101                 realm, err := client.CreateRealm(ctx, token.AccessToken, realmRepresentation)
102                 if err != nil {
103                         return "", err
104                 } else {
105                         fmt.Println("Created realm", realm)
106                 }
107         } else {
108                 fmt.Println("Realm already exists", realmName)
109         }
110
111         flowAlias := "x509 direct grant"
112         flowId := ""
113         flows, err := client.GetAuthenticationFlows(ctx, token.AccessToken, realmName)
114         if err != nil {
115                 fmt.Println("Oh no!, failed to get flows :(")
116         } else {
117                 for _, flow := range flows {
118                         if flow.Alias != nil && *flow.Alias == flowAlias {
119                                 flowId = *flow.ID
120                         }
121                 }
122                 fmt.Println("Retrieved AuthenticationFlow id", flowId)
123         }
124
125         newClient1 := 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"},
136         }
137
138         newClient2 := 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},
152         }
153
154         jwksString := pemtojwks.CreateJWKS("/certs/client_pub.key", "public", "/certs/client.crt") 
155         newClient3 := 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",
169                 },
170         }
171
172         var newClient gocloak.Client
173         if strings.HasPrefix(clientName, "x509") {
174                 newClient = newClient2
175         } else if strings.HasPrefix(clientName, "jwt") {
176                 newClient = newClient3
177         } else {
178                 newClient = newClient1
179         }
180
181         clientId, err := client.CreateClient(ctx, token.AccessToken, realmName, newClient)
182         if err != nil {
183                 fmt.Println("Failed to create client", err)
184                 return "", err
185         } else {
186                 fmt.Println("Created realm client", clientId)
187         }
188
189         newClientRole := gocloak.Role{
190                 Name: gocloak.StringP(clientRoleName),
191         }
192         clientRoleName, err = client.CreateClientRole(ctx, token.AccessToken, realmName, clientId, newClientRole)
193         if err != nil {
194                 return "", err
195         } else {
196                 fmt.Println("Created client role", clientRoleName)
197         }
198
199         user, err := client.GetClientServiceAccount(ctx, token.AccessToken, realmName, clientId)
200         if err != nil {
201                 fmt.Println(err)
202                 panic("Oh no!, failed to get client user :(")
203         } else {
204                 fmt.Println("Service Account user", *user.Username)
205         }
206
207         if strings.HasPrefix(clientName, "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),
213                 }
214
215                 realmUser, err := client.CreateUser(ctx, token.AccessToken, realmName, newUser)
216                 if err != nil {
217                         fmt.Println(err)
218                         panic("Oh no!, failed to create user :(")
219                 } else {
220                         fmt.Println("Created new user", realmUser)
221                 }
222         }
223
224         clientRole, err := client.GetClientRole(ctx, token.AccessToken, realmName, clientId, clientRoleName)
225         if err != nil {
226                 fmt.Println(err)
227                 panic("Oh no!, failed to get client role :(")
228         } else {
229                 fmt.Println("Retrieved client role", clientRoleName)
230         }
231
232         clientRoles := []gocloak.Role{*clientRole}
233         err = client.AddClientRoleToUser(ctx, token.AccessToken, realmName, clientId, *user.ID, clientRoles)
234         if err != nil {
235                 fmt.Println(err)
236                 panic("Oh no!, failed to add client role to user :(")
237         } else {
238                 fmt.Printf("Added %s to %s\n", *clientRole.Name, *user.Username)
239         }
240
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,
255                 },
256         }
257         _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper)
258         if err != nil {
259                 fmt.Println(err)
260                 panic("Oh no!, failed to add client roleampper to client :(")
261         } else {
262                 fmt.Println("Client rolemapper added to client")
263         }
264
265         if strings.HasPrefix(clientName, "x509") {
266                 clientRole := *newClient.ClientID + "." + clientRoleName
267
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{
274                                 "role": clientRole,
275                         },
276                 }
277                 _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper)
278                 if err != nil {
279                         return "", err
280                 } else {
281                         fmt.Println("Created hardcoded-role-mapper for ", clientRole)
282                 }
283         }
284
285         _, err = client.RegenerateClientSecret(ctx, token.AccessToken, realmName, clientId)
286         if err != nil {
287                 return "", err
288         }
289
290         cred, err := client.GetClientSecret(ctx, token.AccessToken, realmName, clientId)
291         if err != nil {
292                 return "", err
293         } else {
294                 fmt.Println("Generated client secret", *cred.Value)
295         }
296
297         return *cred.Value, nil
298 }
299
300 func connectToK8s() *kubernetes.Clientset {
301         config, err := rest.InClusterConfig()
302         if err != nil {
303                 fmt.Println("failed to create K8s config")
304         }
305
306         clientset, err := kubernetes.NewForConfig(config)
307         if err != nil {
308                 fmt.Println("Failed to create K8s clientset")
309         }
310
311         return clientset
312 }
313
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{
320                         Name:      secretName,
321                         Namespace: namespace,
322                         Labels: map[string]string{
323                                 "app": secretName,
324                         },
325                 },
326                 Type:       "Opaque",
327                 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
328         }
329
330         _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
331         if err != nil {
332                 fmt.Println("Failed to create K8s secret.", err)
333         }
334
335         fmt.Println("Created K8s secret successfully")
336 }
337
338 func remove(realmName, clientName string) {
339         adminClient := gocloak.NewClient("http://192.168.49.2:31560")
340         ctx := context.Background()
341         token, err := adminClient.LoginAdmin(ctx, "admin", "admin", "master")
342         if err != nil {
343                 fmt.Println(err)
344         }
345
346         clients, err := adminClient.GetClients(ctx, token.AccessToken, realmName,
347                 gocloak.GetClientsParams{
348                         ClientID: gocloak.StringP(clientName),
349                 },
350         )
351         if err != nil {
352                 panic("List clients failed:" + err.Error())
353         }
354         for _, client := range clients {
355                 err = adminClient.DeleteClient(ctx, token.AccessToken, realmName, *client.ID)
356                 if err != nil {
357                         fmt.Println(err)
358                 } else {
359                         fmt.Println("Deleted client ", clientName)
360                 }
361         }
362
363         userName := realmName + "user"
364         users, err := adminClient.GetUsers(ctx, token.AccessToken, realmName,
365                 gocloak.GetUsersParams{
366                         Username: gocloak.StringP(userName),
367                 })
368         if err != nil {
369                 panic("List users failed:" + err.Error())
370         }
371         for _, user := range users {
372                 err = adminClient.DeleteUser(ctx, token.AccessToken, realmName, *user.ID)
373                 if err != nil {
374                         fmt.Println(err)
375                 } else {
376                         fmt.Println("Deleted user ", userName)
377                 }
378         }
379
380 }
381
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{})
387         if err != nil {
388                 fmt.Println(err)
389         } else {
390                 fmt.Println("Deleted Secret", secretName)
391         }
392 }