Add jwt-proxy functionality
[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 package main
21
22 import (
23         "context"
24         "fmt"
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"
30         "net/http"
31         "rapps/utils/pemtojwks"
32 )
33
34 const (
35         namespace = "istio-nonrtric"
36 )
37
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")
44         var msg string
45         msg, err := create(realmName, clientName, role, authType)
46         if err != nil {
47                 msg = err.Error()
48         }
49         if authType == "client-secret" {
50                 createSecret(msg, clientName, realmName, role, namespace)
51         }
52         // create response binary data
53         data := []byte(msg) // slice of bytes
54         // write `data` to response
55         res.Write(data)
56 }
57
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")
64
65         var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
66         remove(realmName, clientName)
67         if authType == "client-secret" {
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, 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")
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         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"},
136         }
137
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},
152         }
153
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",
169                 },
170         }
171
172         var newClient gocloak.Client
173         if authType == "client-x509" {
174                 newClient = x509Client
175         } else if authType == "client-jwt" {
176                 newClient = jwtClient
177         } else {
178                 newClient = secretClient
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 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),
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 authType == "client-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://keycloak.default:8080")
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 }