3 // ========================LICENSE_START=================================
6 // Copyright (C) 2022-2023: Nordix Foundation
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
12 // http://www.apache.org/licenses/LICENSE-2.0
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===================================
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"
34 "rapps/utils/pemtojwks"
38 namespace = "istio-nonrtric"
41 type Jwttoken struct {
44 Refresh_expires_in int
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"`
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"`
73 Name string `json:"name,omitempty"`
77 ID string `json:"id,omitempty"`
78 Username string `json:"username,omitempty"`
79 Email string `json:"email,omitempty"`
80 Enabled bool `json:"enabled"`
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"`
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"`
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"`
106 type Execution struct {
107 Provider string `json:"provider,omitempty"`
110 type AuthenticatorConfigRepresentation struct {
111 Alias string `json:"alias,omitempty"`
112 Config map[string]string `json:"config,omitempty"`
115 var keycloakUrl string = "http://keycloak:8080"
117 var flowAlias string = "x509 direct grant"
119 func createClient(res http.ResponseWriter, req *http.Request) {
120 body, err := ioutil.ReadAll(req.Body)
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"]
136 msg, err = create(realmName, clientName, role, authType, tlsCrt, email, subjectDN, mappingSource)
140 if authType == "client-secret" {
141 createSecret(msg, clientName, realmName, namespace)
143 // create response binary data
144 data := []byte(msg) // slice of bytes
145 // write `data` to response
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")
155 var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
156 remove(realmName, clientName)
157 if authType == "client-secret" {
158 removeSecret(namespace, clientName)
160 // create response binary data
161 data := []byte(msg) // slice of bytes
162 // write `data` to response
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)
174 func getAdminToken() {
175 var resp = &http.Response{}
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}})
185 panic("Something wrong with the credentials or url ")
187 defer resp.Body.Close()
188 body, err := ioutil.ReadAll(resp.Body)
189 json.Unmarshal([]byte(body), &token)
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)
198 client := &http.Client{}
199 resp, err := client.Do(req)
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
210 func create(realmName, clientName, clientRoleName, authType, tlsCrt, email, subjectDN, mappingSource string) (string, error) {
212 var userId string = ""
213 var jsonValue []byte = []byte{}
214 restUrl := keycloakUrl + "/realms/" + realmName
215 statusCode, _ := sendRequest("GET", restUrl, nil)
217 if statusCode != 200 {
218 realmRepresentation := RealmRepresentation{
221 DisplayName: realmName,
224 restUrl := keycloakUrl + "/admin/realms"
225 jsonValue, _ := json.Marshal(realmRepresentation)
226 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
229 var flowId string = ""
230 if authType == "client-x509" {
231 flowId = getFlowId(realmName)
233 createx509Flow(realmName, mappingSource)
234 flowId = getFlowId(realmName)
237 ID: realmName + "user",
238 Username: realmName + "user",
242 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users"
243 jsonValue, _ = json.Marshal(newUser)
244 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
245 userId = getUserId(realmName, realmName+"user")
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)
253 clientId, clientSecret := getClientInfo(realmName, clientName)
255 newClientRole := Role{
256 Name: clientRoleName,
258 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles"
259 jsonValue, _ = json.Marshal(newClientRole)
260 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
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)
269 roleId := fmt.Sprintf("%v", roles["id"])
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)
279 userId = fmt.Sprintf("%v", serviceAccount["id"])
282 roleRepresentation := RoleRepresentation{
284 Name: clientRoleName,
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)
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",
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
315 func getClient(authType, clientName, flowId, tlsCrt, subjectDN string) 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}
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",
352 func getClientInfo(realmName, clientName string) (string, string) {
353 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients?clientId=" + clientName
354 _, data := sendRequest("GET", restUrl, nil)
356 clients := make([]map[string]interface{}, 0)
357 err := json.Unmarshal([]byte(data), &clients)
361 clientId := fmt.Sprintf("%v", clients[0]["id"])
362 clientSecret := fmt.Sprintf("%v", clients[0]["secret"])
363 return clientId, clientSecret
366 func createx509Flow(realmName, mappingSource string) {
367 var jsonValue []byte = []byte{}
368 authenticationFlowRepresentation := AuthenticationFlowRepresentation{
370 Description: "OpenID Connect Resource Owner Grant",
371 ProviderId: "basic-flow",
374 AthenticationExecutions: []string{},
376 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
377 jsonValue, _ = json.Marshal(authenticationFlowRepresentation)
378 sendRequest("POST", restUrl, jsonValue)
380 execution := Execution{
381 Provider: "direct-grant-auth-x509-username",
383 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions/execution"
384 jsonValue, _ = json.Marshal(execution)
385 sendRequest("POST", restUrl, jsonValue)
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)
394 executionId := fmt.Sprintf("%v", executionInfo[0]["id"])
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",
414 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/executions/" + executionId + "/config"
415 jsonValue, _ = json.Marshal(authenticatorConfigRepresentation)
416 sendRequest("POST", restUrl, jsonValue)
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)
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 {
439 func connectToK8s() *kubernetes.Clientset {
440 config, err := rest.InClusterConfig()
442 fmt.Println("failed to create K8s config")
445 clientset, err := kubernetes.NewForConfig(config)
447 fmt.Println("Failed to create K8s clientset")
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{
460 Namespace: namespace,
461 Labels: map[string]string{
466 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
469 _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
471 fmt.Println("Failed to create K8s secret.", err)
474 fmt.Println("Created K8s secret successfully")
477 func remove(realmName, clientName string) {
479 clientId, _ := getClientInfo(realmName, clientName)
481 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId
482 sendRequest("DELETE", restUrl, nil)
484 var userId string = ""
485 userName := realmName + "user"
486 userId = getUserId(realmName, userName)
488 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId
489 sendRequest("DELETE", restUrl, nil)
492 flowId := getFlowId(realmName)
494 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowId
495 sendRequest("DELETE", restUrl, nil)
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)
509 userId = fmt.Sprintf("%v", user[0]["id"])
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{})
522 fmt.Println("Deleted Secret", secretName)