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 query := req.URL.Query()
121 realmName := query.Get("realm")
122 clientName := query.Get("name")
123 role := query.Get("role")
124 authType := query.Get("authType")
126 msg, err := create(realmName, clientName, role, authType)
130 if authType == "client-secret" {
131 createSecret(msg, clientName, realmName, namespace)
133 // create response binary data
134 data := []byte(msg) // slice of bytes
135 // write `data` to response
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")
145 var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
146 remove(realmName, clientName)
147 if authType == "client-secret" {
148 removeSecret(namespace, clientName)
150 // create response binary data
151 data := []byte(msg) // slice of bytes
152 // write `data` to response
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)
164 func getAdminToken() {
165 var resp = &http.Response{}
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}})
175 panic("Something wrong with the credentials or url ")
177 defer resp.Body.Close()
178 body, err := ioutil.ReadAll(resp.Body)
179 json.Unmarshal([]byte(body), &token)
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)
188 client := &http.Client{}
189 resp, err := client.Do(req)
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
200 func create(realmName, clientName, clientRoleName, authType string) (string, error) {
202 var userId string = ""
203 var jsonValue []byte = []byte{}
204 restUrl := keycloakUrl + "/realms/" + realmName
205 statusCode, _ := sendRequest("GET", restUrl, nil)
207 if statusCode != 200 {
208 realmRepresentation := RealmRepresentation{
211 DisplayName: realmName,
214 restUrl := keycloakUrl + "/admin/realms"
215 jsonValue, _ := json.Marshal(realmRepresentation)
216 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
219 var flowId string = ""
220 if authType == "client-x509" {
221 flowId = getFlowId(realmName)
223 createx509Flow(realmName)
224 flowId = getFlowId(realmName)
227 ID: realmName + "user",
228 Username: realmName + "user",
229 Email: "client@mail.com",
232 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users"
233 jsonValue, _ = json.Marshal(newUser)
234 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
235 userId = getUserId(realmName, realmName+"user")
238 newClient := getClient(authType, clientName, flowId)
239 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients"
240 jsonValue, _ = json.Marshal(newClient)
241 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
243 clientId, clientSecret := getClientInfo(realmName, clientName)
245 newClientRole := Role{
246 Name: clientRoleName,
248 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles"
249 jsonValue, _ = json.Marshal(newClientRole)
250 statusCode, _ = sendRequest("POST", restUrl, jsonValue)
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)
259 roleId := fmt.Sprintf("%v", roles["id"])
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)
269 userId = fmt.Sprintf("%v", serviceAccount["id"])
272 roleRepresentation := RoleRepresentation{
274 Name: clientRoleName,
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)
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",
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
305 func getClient(authType, clientName, flowId string) 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}
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",
342 func getClientInfo(realmName, clientName string) (string, string) {
343 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients?clientId=" + clientName
344 _, data := sendRequest("GET", restUrl, nil)
346 clients := make([]map[string]interface{}, 0)
347 err := json.Unmarshal([]byte(data), &clients)
351 clientId := fmt.Sprintf("%v", clients[0]["id"])
352 clientSecret := fmt.Sprintf("%v", clients[0]["secret"])
353 return clientId, clientSecret
356 func createx509Flow(realmName string) {
357 var jsonValue []byte = []byte{}
358 authenticationFlowRepresentation := AuthenticationFlowRepresentation{
360 Description: "OpenID Connect Resource Owner Grant",
361 ProviderId: "basic-flow",
364 AthenticationExecutions: []string{},
366 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows"
367 jsonValue, _ = json.Marshal(authenticationFlowRepresentation)
368 sendRequest("POST", restUrl, jsonValue)
370 execution := Execution{
371 Provider: "direct-grant-auth-x509-username",
373 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions/execution"
374 jsonValue, _ = json.Marshal(execution)
375 sendRequest("POST", restUrl, jsonValue)
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)
384 executionId := fmt.Sprintf("%v", executionInfo[0]["id"])
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",
404 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/executions/" + executionId + "/config"
405 jsonValue, _ = json.Marshal(authenticatorConfigRepresentation)
406 sendRequest("POST", restUrl, jsonValue)
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)
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 {
429 func connectToK8s() *kubernetes.Clientset {
430 config, err := rest.InClusterConfig()
432 fmt.Println("failed to create K8s config")
435 clientset, err := kubernetes.NewForConfig(config)
437 fmt.Println("Failed to create K8s clientset")
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{
450 Namespace: namespace,
451 Labels: map[string]string{
456 StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
459 _, err := secrets.Create(context.TODO(), secret, metav1.CreateOptions{})
461 fmt.Println("Failed to create K8s secret.", err)
464 fmt.Println("Created K8s secret successfully")
467 func remove(realmName, clientName string) {
469 clientId, _ := getClientInfo(realmName, clientName)
471 restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId
472 sendRequest("DELETE", restUrl, nil)
474 var userId string = ""
475 userName := realmName + "user"
476 userId = getUserId(realmName, userName)
478 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId
479 sendRequest("DELETE", restUrl, nil)
482 flowId := getFlowId(realmName)
484 restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowId
485 sendRequest("DELETE", restUrl, nil)
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)
499 userId = fmt.Sprintf("%v", user[0]["id"])
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{})
512 fmt.Println("Deleted Secret", secretName)