From df61b02070956cac9ec7429281dc78ba853b46ed Mon Sep 17 00:00:00 2001 From: ktimoney Date: Tue, 4 Apr 2023 17:17:35 +0100 Subject: [PATCH] Update keycloak version Change-Id: Idc3113c4886d0870647eb5be1598ecd4fb824d9f Issue-ID: NONRTRIC-857 Signed-off-by: ktimoney --- service-exposure/README.md | 4 +- service-exposure/certs/client_certs.sh | 27 +- service-exposure/certs/server_certs.sh | 49 +- service-exposure/keycloak.yaml | 109 ++-- service-exposure/rapps-helm-installer.go | 5 +- service-exposure/rapps-jwt.go | 14 +- service-exposure/rapps-keycloak-mgr.go | 570 +++++++++++++-------- .../templates/AuthorizationPolicy-template.txt | 4 +- .../templates/RequestAuthentication-template.txt | 4 +- service-exposure/utils/pemtojwks/pemtojwks.go | 24 +- 10 files changed, 500 insertions(+), 310 deletions(-) diff --git a/service-exposure/README.md b/service-exposure/README.md index 6f34b36e..938e4ae3 100644 --- a/service-exposure/README.md +++ b/service-exposure/README.md @@ -1,6 +1,6 @@ # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ rapps-webhook.yaml: "-hostPath", "/var/rapps/certs" or change them to match your own setup. The certs directory contains 3 shell scripts for creating the server, client and webhook certs: server_certs.sh, client_certs.sh and webhook_certs.sh -Certs generated by the server_certs.sh script: rootCA.crt, tls.crt and tls.key go in the "/var/keycloak/certs" directory +Keystores/Truststores generated by the server_certs.sh script: server.keystore and server.truststore go in the "/var/keycloak/certs" directory Certs generated by the client_certs.sh script: client.crt, client.key and rootCA.crt go in the "/var/rapps/certs" directory The webhook_certs.sh script generates certs for use in the MutatingWebhookConfiguration.yaml and the rapps-webhook.yaml files. To configure MutatingWebhookConfiguration.yaml run the following commands: diff --git a/service-exposure/certs/client_certs.sh b/service-exposure/certs/client_certs.sh index fbda1ec6..bad7583c 100755 --- a/service-exposure/certs/client_certs.sh +++ b/service-exposure/certs/client_certs.sh @@ -1,7 +1,7 @@ #!/bin/sh # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,14 +19,31 @@ # ============LICENSE_END========================================================= # - CLIENT_SUBJECT="/C=IE/ST=Dublin/L=Dublin/O=Keycloak/OU=Keycloak/CN=localhost/emailAddress=client@mail.com" PW=changeit +CERTNAME=client +IP=$(minikube ip) +DAYS=3650 +rm ${CERTNAME}.key ${CERTNAME}.csr ${CERTNAME}.crt ${CERTNAME}.p12 ${CERTNAME}.pem ${CERTNAME}_pub.key 2>/dev/null echo $PW > secretfile.txt -openssl req -new -newkey rsa:4096 -nodes -keyout client.key -subj "$CLIENT_SUBJECT" -out client.csr +echo "subjectKeyIdentifier = hash" > x509.ext +echo "authorityKeyIdentifier = keyid:always,issuer:always" >> x509.ext +echo "basicConstraints = CA:TRUE" >> x509.ext +echo "keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign" >> x509.ext +echo "subjectAltName = DNS.1:localhost, IP.1:127.0.0.1, DNS.2:minikube, IP.2:${IP}, DNS.3:keycloak.default, DNS.4:keycloak.est.tech, DNS.5:keycloak" >> x509.ext +echo "issuerAltName = issuer:copy" >> x509.ext + +openssl req -new -newkey rsa:4096 -nodes -keyout ${CERTNAME}.key -subj "$CLIENT_SUBJECT" -out ${CERTNAME}.csr + +openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in ${CERTNAME}.csr -passin file:secretfile.txt -out ${CERTNAME}.crt -days $DAYS -CAcreateserial -extfile x509.ext + + +openssl pkcs12 -export -clcerts -in ${CERTNAME}.crt -inkey ${CERTNAME}.key -passout file:secretfile.txt -out ${CERTNAME}.p12 + +openssl pkcs12 -in ${CERTNAME}.p12 -password pass:$PW -passout file:secretfile.txt -out ${CERTNAME}.pem -clcerts -nodes -openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in client.csr -passin file:secretfile.txt -out client.crt -days 365 -CAcreateserial +openssl rsa -in ${CERTNAME}.key -outform PEM -pubout -out ${CERTNAME}_pub.key -rm secretfile.txt 2>/dev/null +rm secretfile.txt x509.ext 2>/dev/null diff --git a/service-exposure/certs/server_certs.sh b/service-exposure/certs/server_certs.sh index a6063683..15cccb74 100755 --- a/service-exposure/certs/server_certs.sh +++ b/service-exposure/certs/server_certs.sh @@ -1,7 +1,7 @@ #!/bin/sh # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,19 +23,48 @@ CA_SUBJECT="/C=IE/ST=Dublin/L=Dublin/O=Keycloak/OU=Keycloak/CN=localhost/emailAddress=ca@mail.com" SERVER_SUBJECT="/C=IE/ST=Dublin/L=Dublin/O=Keycloak/OU=Keycloak/CN=localhost/emailAddress=server@mail.com" PW=changeit +CERTNAME=tls +CANAME=rootCA +IP=$(minikube ip) +DAYS=3650 +TRUSTSTORE=server.truststore +KEYSTORE=server.keystore +STORETYPE=PKCS12 +rm $TRUSTSTORE $KEYSTORE ${CANAME}.key ${CANAME}.crt ${CERTNAME}.key ${CERTNAME}.csr ${CERTNAME}.crt ${CERTNAME}.p12 2>/dev/null echo $PW > secretfile.txt -openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -subj "$CA_SUBJECT" -passout file:secretfile.txt -out rootCA.crt +openssl req -x509 -sha256 -days $DAYS -newkey rsa:4096 -keyout ${CANAME}.key -subj "$CA_SUBJECT" -passout file:secretfile.txt -out ${CANAME}.crt -openssl req -new -newkey rsa:4096 -keyout tls.key -subj "$SERVER_SUBJECT" -out tls.csr -nodes +openssl req -new -newkey rsa:4096 -keyout ${CERTNAME}.key -subj "$SERVER_SUBJECT" -out ${CERTNAME}.csr -nodes -echo "authorityKeyIdentifier=keyid,issuer" > openssl.ext -echo "basicConstraints=CA:FALSE" >> openssl.ext -echo "subjectAltName = @alt_names" >> openssl.ext -echo "[alt_names]" >> openssl.ext -echo "DNS.1 = localhost" >> openssl.ext +echo "subjectKeyIdentifier = hash" > x509.ext +echo "authorityKeyIdentifier = keyid:always,issuer:always" >> x509.ext +echo "basicConstraints = CA:TRUE" >> x509.ext +echo "keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign" >> x509.ext +echo "subjectAltName = DNS.1:localhost, IP.1:127.0.0.1, DNS.2:minikube, IP.2:${IP}, DNS.3:keycloak.default, DNS.4:keycloak.est.tech, DNS.5:keycloak" >> x509.ext +echo "issuerAltName = issuer:copy" >> x509.ext +echo "[ ca ]" >> x509.ext +echo "# X509 extensions for a ca" >> x509.ext +echo "keyUsage = critical, cRLSign, keyCertSign" >> x509.ext +echo "basicConstraints = CA:TRUE, pathlen:0" >> x509.ext +echo "subjectKeyIdentifier = hash" >> x509.ext +echo "authorityKeyIdentifier = keyid:always,issuer:always" >> x509.ext +echo "" >> x509.ext +echo "[ server ]" >> x509.ext +echo "# X509 extensions for a server" >> x509.ext +echo "keyUsage = critical,digitalSignature,keyEncipherment" >> x509.ext +echo "extendedKeyUsage = serverAuth,clientAuth" >> x509.ext +echo "basicConstraints = critical,CA:FALSE" >> x509.ext +echo "subjectKeyIdentifier = hash" >> x509.ext +echo "authorityKeyIdentifier = keyid,issuer:always" >> x509.ext -openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in tls.csr -passin file:secretfile.txt -out tls.crt -days 365 -CAcreateserial -ext openssl.ext +openssl x509 -req -CA ${CANAME}.crt -CAkey ${CANAME}.key -in ${CERTNAME}.csr -passin file:secretfile.txt -out ${CERTNAME}.crt -days $DAYS -CAcreateserial -extfile x509.ext -rm secretfile.txt openssl.ext 2>/dev/null +keytool -import -trustcacerts -file ${CANAME}.crt -keystore $TRUSTSTORE -storepass $PW -storetype $STORETYPE -noprompt + +openssl pkcs12 -export -clcerts -in ${CERTNAME}.crt -inkey ${CERTNAME}.key -passout file:secretfile.txt -out ${CERTNAME}.p12 + +keytool -importkeystore -srckeystore ${CERTNAME}.p12 -srcstorepass $PW -srcstoretype $STORETYPE -destkeystore $KEYSTORE -deststorepass $PW -deststoretype $STORETYPE + +rm secretfile.txt x509.ext 2>/dev/null diff --git a/service-exposure/keycloak.yaml b/service-exposure/keycloak.yaml index 2beace26..b6a18c3a 100644 --- a/service-exposure/keycloak.yaml +++ b/service-exposure/keycloak.yaml @@ -1,6 +1,6 @@ # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,24 +20,23 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: keycloak + name: keycloak namespace: default --- apiVersion: v1 kind: Service metadata: name: keycloak - namespace: default labels: app: keycloak spec: type: ExternalName - externalName: keycloak.local + externalName: keycloak.local ports: - name: http port: 8080 targetPort: 8080 - nodePort: 31560 + nodePort: 31560 - name: https port: 8443 targetPort: 8443 @@ -66,42 +65,48 @@ spec: initContainers: - name: init-postgres image: busybox - imagePullPolicy: IfNotPresent + imagePullPolicy: IfNotPresent command: ['sh', '-c', 'until nc -vz postgres 5432; do echo waiting for postgres db; sleep 2; done;'] - serviceAccountName: keycloak + serviceAccountName: keycloak containers: - name: keycloak - image: quay.io/keycloak/keycloak:16.1.1 - imagePullPolicy: IfNotPresent + image: quay.io/keycloak/keycloak:latest + imagePullPolicy: IfNotPresent + args: [ + 'start', + '--https-key-store-file=/etc/x509/https/server.keystore', + '--https-key-store-password=changeit', + '--https-key-store-type=PKCS12', + '--https-trust-store-file=/etc/x509/https/server.truststore', + '--https-trust-store-password=changeit', + '--https-trust-store-type=PKCS12', + '--https-client-auth=request', + '--http-enabled=true' + ] + env: - - name: KEYCLOAK_USER - value: "admin" - - name: KEYCLOAK_PASSWORD - value: "admin" - - name: KEYCLOAK_HTTPS_PORT - value: "8443" - - name: PROXY_ADDRESS_FORWARDING + - name : X509_CA_BUNDLE + value: /etc/x509/https/rootCA.crt + - name : KEYCLOAK_ADMIN + value: admin + - name : KEYCLOAK_ADMIN_PASSWORD + value: admin + - name : KC_DB + value: postgres + - name : KC_DB_URL + value: "jdbc:postgresql://postgres:5432/keycloak" + - name : KC_DB_USERNAME + value: keycloak + - name : KC_DB_PASSWORD + value: keycloak + - name : KC_HOSTNAME + value: keycloak + - name : MY_PROVIDER_JAR_URL + value: /opt/jboss/keycloak/standalone/deployments/authz-js-policies.jar + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_METRICS_ENABLED value: "true" - - name: MANAGEMENT_USER - value: "wildfly-admin" - - name: MANAGEMENT_PASSWORD - value: "secret" - - name: INGRESS_ENABLED - value: "false" - - name: DB_VENDOR - value: "postgres" - - name: DB_ADDR - value: "postgres" - - name: DB_PORT - value: "5432" - - name: DB_DATABASE - value: "keycloak" - - name: DB_USER - value: "keycloak" - - name : DB_PASSWORD - value: "keycloak" - - name : X509_CA_BUNDLE - value: /etc/x509/https/rootCA.crt ports: - name: http containerPort: 8080 @@ -109,22 +114,28 @@ spec: containerPort: 8443 readinessProbe: httpGet: - path: /auth/realms/master - port: 8080 + scheme: HTTPS + path: /health/ready + port: 8443 volumeMounts: - - name: keycloak-certs - mountPath: /etc/x509/https + - name: keycloak-certs + mountPath: /etc/x509/https + - name: authz-js-policies + mountPath: /opt/jboss/keycloak/standalone/deployments/authz-js-policies.jar volumes: - - name: keycloak-certs + - name: keycloak-certs hostPath: - path: /var/keycloak/certs + path: /var/keycloak/certs type: Directory + - name: authz-js-policies + hostPath: + path: /var/keycloak/deployments/authz-js-policies.jar + type: File --- apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: kcgateway - namespace: default spec: selector: istio: ingressgateway # use istio default ingress gateway @@ -136,7 +147,7 @@ spec: tls: mode: PASSTHROUGH hosts: - - keycloak.oran.org + - keycloak.est.tech - port: number: 80 name: http @@ -148,17 +159,16 @@ apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: keycloak-tls-vs - namespace: default spec: hosts: - - keycloak.oran.org + - keycloak.est.tech gateways: - kcgateway tls: - match: - port: 443 sniHosts: - - keycloak.oran.org + - keycloak.est.tech route: - destination: host: keycloak.default.svc.cluster.local @@ -169,17 +179,16 @@ apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: keycloak-vs - namespace: default spec: hosts: - "*" gateways: - - kcgateway + - kcgateway http: - name: "keycloak-routes" match: - uri: - prefix: "/auth" + prefix: "/realms" route: - destination: port: diff --git a/service-exposure/rapps-helm-installer.go b/service-exposure/rapps-helm-installer.go index 9dec9f7d..4a5d4649 100644 --- a/service-exposure/rapps-helm-installer.go +++ b/service-exposure/rapps-helm-installer.go @@ -2,7 +2,7 @@ // ========================LICENSE_START================================= // O-RAN-SC // %% -// Copyright (C) 2022: Nordix Foundation +// Copyright (C) 2022-2023: Nordix Foundation // %% // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -225,7 +225,6 @@ func runUninstall(res http.ResponseWriter, req *http.Request) { func uninstallSecurity(rapp Rapp, chartName string) error { var url string var params string - role := rapp.Roles[0].Role realm := rapp.Realm client := rapp.Client authenticator := rapp.Authenticator @@ -243,7 +242,7 @@ func uninstallSecurity(rapp Rapp, chartName string) error { // remove keycloak client fmt.Println("Removing keycloak client") url = "http://rapps-keycloak-mgr.default/remove?" - params = "name=" + client + "&realm=" + realm + "&role=" + role + "&authType=" + authenticator + params = "name=" + client + "&realm=" + realm + "&authType=" + authenticator url += params _, err = http.Get(url) if err != nil { diff --git a/service-exposure/rapps-jwt.go b/service-exposure/rapps-jwt.go index d220458b..ae004327 100644 --- a/service-exposure/rapps-jwt.go +++ b/service-exposure/rapps-jwt.go @@ -2,7 +2,7 @@ // ========================LICENSE_START================================= // O-RAN-SC // %% -// Copyright (C) 2022: Nordix Foundation +// Copyright (C) 2022-2023: Nordix Foundation // %% // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ func getToken(res http.ResponseWriter, req *http.Request) { clientId = req.Header.Get("client") realmName = req.Header.Get("realm") namespace = req.Header.Get("ns") - keycloakUrl := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName + "/protocol/openid-connect/token" + keycloakUrl := "http://" + keycloakHost + ":" + keycloakPort + "/realms/" + realmName + "/protocol/openid-connect/token" fmt.Printf("Making token request to %s\n", keycloakUrl) res.Header().Set("Content-type", "application/json") res.Header().Set("Authorization", "") @@ -78,6 +78,8 @@ func getToken(res http.ResponseWriter, req *http.Request) { if authenticator == "client-jwt" { resp, err = getJwtToken(keycloakUrl, clientId) } else if authenticator == "client-x509" { + keycloakPort = "443" + keycloakUrl := "https://" + keycloakAlias + ":" + keycloakPort + "/realms/" + realmName + "/protocol/openid-connect/token" resp, err = getx509Token(keycloakUrl, clientId) } else { resp, err = getSecretToken(keycloakUrl, clientId) @@ -119,8 +121,10 @@ func getJwtToken(keycloakUrl, clientId string) (*http.Response, error) { } func getClientAssertion() string { - realm := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName - clientAssertion := generatejwt.CreateJWT("/certs/client.key", "", clientId, realm) + //aud := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName + //aud := "http://keycloak/auth/realms/" + realmName + aud := "https://keycloak:8443/realms/" + realmName + clientAssertion := generatejwt.CreateJWT("/certs/client.key", "", clientId, aud) return clientAssertion } @@ -214,7 +218,7 @@ func health(res http.ResponseWriter, req *http.Request) { func main() { flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host") flag.StringVar(&keycloakPort, "keycloakPort", "80", "Keycloak Port") - flag.StringVar(&keycloakAlias, "keycloakAlias", "keycloak.oran.org", "Keycloak URL Alias") + flag.StringVar(&keycloakAlias, "keycloakAlias", "keycloak.est.tech", "Keycloak URL Alias") flag.Parse() healthHandler := http.HandlerFunc(health) diff --git a/service-exposure/rapps-keycloak-mgr.go b/service-exposure/rapps-keycloak-mgr.go index 35e503dc..6c72d87c 100644 --- a/service-exposure/rapps-keycloak-mgr.go +++ b/service-exposure/rapps-keycloak-mgr.go @@ -1,33 +1,36 @@ // - -// ========================LICENSE_START================================= -// O-RAN-SC -// %% -// Copyright (C) 2022: Nordix Foundation -// %% -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// ========================LICENSE_START================================= +// O-RAN-SC +// %% +// Copyright (C) 2022-2023: Nordix Foundation +// %% +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ========================LICENSE_END=================================== +// http://www.apache.org/licenses/LICENSE-2.0 // +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ========================LICENSE_END=================================== package main import ( + "bytes" "context" + "encoding/json" "fmt" - "github.com/Nerzal/gocloak/v10" + "io/ioutil" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubernetes "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "net/http" + "net/url" "rapps/utils/pemtojwks" ) @@ -35,6 +38,84 @@ const ( namespace = "istio-nonrtric" ) +type Jwttoken struct { + Access_token string + Expires_in int + Refresh_expires_in int + Refresh_token string + Token_type string + Not_before_policy int + Session_state string + Scope string +} + +type RealmRepresentation struct { + Id string `json:"id,omitempty"` + Realm string `json:"realm,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Enabled bool `json:"enabled"` +} + +type Client struct { + ClientID string `json:"clientId,omitempty"` + Enabled bool `json:"enabled,omitempty"` + DirectAccessGrantsEnabled bool `json:"directAccessGrantsEnabled,omitempty"` + BearerOnly bool `json:"bearerOnly,omitempty"` + PublicClient bool `json:"publicClient,omitempty"` + ServiceAccountsEnabled bool `json:"serviceAccountsEnabled,omitempty"` + ClientAuthenticatorType string `json:"clientAuthenticatorType,omitempty"` + DefaultClientScopes []string `json:"defaultClientScopes,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` + AuthenticationFlowBindingOverrides map[string]string `json:"authenticationFlowBindingOverrides,omitempty"` +} + +type Role struct { + Name string `json:"name,omitempty"` +} + +type User struct { + ID string `json:"id,omitempty"` + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` + Enabled bool `json:"enabled"` +} + +type ProtocolMapperRepresentation struct { + Name string `json:"name,omitempty"` + Protocol string `json:"protocol,omitempty"` + ProtocolMapper string `json:"protocolMapper,omitempty"` + Config map[string]string `json:"config,omitempty"` +} + +type RoleRepresentation struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Composite bool `json:"composite"` + ClientRole bool `json:"clientRole"` +} + +type AuthenticationFlowRepresentation struct { + Alias string `json:"alias,omitempty"` + Description string `json:"description,omitempty"` + ProviderId string `json:"providerId,omitempty"` + TopLevel bool `json:"topLevel"` + BuiltIn bool `json:"builtIn"` + AthenticationExecutions []string `json:"authenticationExecutions,omitempty"` +} + +type Execution struct { + Provider string `json:"provider,omitempty"` +} + +type AuthenticatorConfigRepresentation struct { + Alias string `json:"alias,omitempty"` + Config map[string]string `json:"config,omitempty"` +} + +var keycloakUrl string = "http://keycloak:8080" +var token Jwttoken +var flowAlias string = "x509 direct grant" + func createClient(res http.ResponseWriter, req *http.Request) { query := req.URL.Query() realmName := query.Get("realm") @@ -47,7 +128,7 @@ func createClient(res http.ResponseWriter, req *http.Request) { msg = err.Error() } if authType == "client-secret" { - createSecret(msg, clientName, realmName, role, namespace) + createSecret(msg, clientName, realmName, namespace) } // create response binary data data := []byte(msg) // slice of bytes @@ -59,13 +140,12 @@ func removeClient(res http.ResponseWriter, req *http.Request) { query := req.URL.Query() realmName := query.Get("realm") clientName := query.Get("name") - role := query.Get("role") authType := query.Get("authType") var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm" remove(realmName, clientName) if authType == "client-secret" { - removeSecret(namespace, role) + removeSecret(namespace, clientName) } // create response binary data data := []byte(msg) // slice of bytes @@ -81,220 +161,269 @@ func main() { http.ListenAndServe(":9000", nil) } -func create(realmName, clientName, clientRoleName, authType string) (string, error) { - client := gocloak.NewClient("http://keycloak.default:8080") - ctx := context.Background() - token, err := client.LoginAdmin(ctx, "admin", "admin", "master") +func getAdminToken() { + var resp = &http.Response{} + var err error + username := "admin" + password := "admin" + clientId := "admin-cli" + restUrl := keycloakUrl + "/realms/master/protocol/openid-connect/token" + resp, err = http.PostForm(restUrl, + url.Values{"username": {username}, "password": {password}, "grant_type": {"password"}, "client_id": {clientId}}) if err != nil { - return "", err + fmt.Println(err) + panic("Something wrong with the credentials or url ") } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + json.Unmarshal([]byte(body), &token) +} - _, err = client.GetRealm(ctx, token.AccessToken, realmName) - if err != nil { - realmRepresentation := gocloak.RealmRepresentation{ - ID: gocloak.StringP(realmName), - Realm: gocloak.StringP(realmName), - DisplayName: gocloak.StringP(realmName), - Enabled: gocloak.BoolP(true), - } - - realm, err := client.CreateRealm(ctx, token.AccessToken, realmRepresentation) - if err != nil { - return "", err - } else { - fmt.Println("Created realm", realm) - } - } else { - fmt.Println("Realm already exists", realmName) - } +func sendRequest(method, url string, data []byte) (int, string) { + fmt.Printf("Sending %s request to %s\n", method, url) + req, err := http.NewRequest(method, url, bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token.Access_token) - flowAlias := "x509 direct grant" - flowId := "" - flows, err := client.GetAuthenticationFlows(ctx, token.AccessToken, realmName) + client := &http.Client{} + resp, err := client.Do(req) if err != nil { - fmt.Println("Oh no!, failed to get flows :(") - } else { - for _, flow := range flows { - if flow.Alias != nil && *flow.Alias == flowAlias { - flowId = *flow.ID - } - } - fmt.Println("Retrieved AuthenticationFlow id", flowId) - } - - secretClient := gocloak.Client{ - ClientID: gocloak.StringP(clientName), - Enabled: gocloak.BoolP(true), - DirectAccessGrantsEnabled: gocloak.BoolP(true), - BearerOnly: gocloak.BoolP(false), - PublicClient: gocloak.BoolP(false), - ServiceAccountsEnabled: gocloak.BoolP(true), - ClientAuthenticatorType: gocloak.StringP("client-secret"), - DefaultClientScopes: &[]string{"email"}, - Attributes: &map[string]string{"use.refresh.tokens": "true", - "client_credentials.use_refresh_token": "true"}, - } - - x509Client := gocloak.Client{ - ClientID: gocloak.StringP(clientName), - Enabled: gocloak.BoolP(true), - DirectAccessGrantsEnabled: gocloak.BoolP(true), - BearerOnly: gocloak.BoolP(false), - PublicClient: gocloak.BoolP(false), - ServiceAccountsEnabled: gocloak.BoolP(true), - ClientAuthenticatorType: gocloak.StringP("client-x509"), - DefaultClientScopes: &[]string{"openid", "profile", "email"}, - Attributes: &map[string]string{"use.refresh.tokens": "true", - "client_credentials.use_refresh_token": "true", - "x509.subjectdn": ".*client@mail.com.*", - "x509.allow.regex.pattern.comparison": "true"}, - AuthenticationFlowBindingOverrides: &map[string]string{"direct_grant": flowId}, + panic(err) } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + respString := string(body) + fmt.Println("response Status:", resp.Status) + return resp.StatusCode, respString +} - jwksString := pemtojwks.CreateJWKS("/certs/client.crt") - jwtClient := gocloak.Client{ - ClientID: gocloak.StringP(clientName), - Enabled: gocloak.BoolP(true), - DirectAccessGrantsEnabled: gocloak.BoolP(true), - BearerOnly: gocloak.BoolP(false), - PublicClient: gocloak.BoolP(false), - ServiceAccountsEnabled: gocloak.BoolP(true), - ClientAuthenticatorType: gocloak.StringP("client-jwt"), - DefaultClientScopes: &[]string{"email"}, - Attributes: &map[string]string{"token.endpoint.auth.signing.alg": "RS256", - "use.jwks.string": "true", - "jwks.string": jwksString, - "use.refresh.tokens": "true", - "client_credentials.use_refresh_token": "true", - }, +func create(realmName, clientName, clientRoleName, authType string) (string, error) { + getAdminToken() + var userId string = "" + var jsonValue []byte = []byte{} + restUrl := keycloakUrl + "/realms/" + realmName + statusCode, _ := sendRequest("GET", restUrl, nil) + + if statusCode != 200 { + realmRepresentation := RealmRepresentation{ + Id: realmName, + Realm: realmName, + DisplayName: realmName, + Enabled: true, + } + restUrl := keycloakUrl + "/admin/realms" + jsonValue, _ := json.Marshal(realmRepresentation) + statusCode, _ = sendRequest("POST", restUrl, jsonValue) } - var newClient gocloak.Client + var flowId string = "" if authType == "client-x509" { - newClient = x509Client - } else if authType == "client-jwt" { - newClient = jwtClient - } else { - newClient = secretClient + flowId = getFlowId(realmName) + if flowId == "" { + createx509Flow(realmName) + flowId = getFlowId(realmName) + } + newUser := User{ + ID: realmName + "user", + Username: realmName + "user", + Email: "client@mail.com", + Enabled: true, + } + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users" + jsonValue, _ = json.Marshal(newUser) + statusCode, _ = sendRequest("POST", restUrl, jsonValue) + userId = getUserId(realmName, realmName+"user") } - clientId, err := client.CreateClient(ctx, token.AccessToken, realmName, newClient) - if err != nil { - fmt.Println("Failed to create client", err) - return "", err - } else { - fmt.Println("Created realm client", clientId) - } + newClient := getClient(authType, clientName, flowId) + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients" + jsonValue, _ = json.Marshal(newClient) + statusCode, _ = sendRequest("POST", restUrl, jsonValue) - newClientRole := gocloak.Role{ - Name: gocloak.StringP(clientRoleName), - } - clientRoleName, err = client.CreateClientRole(ctx, token.AccessToken, realmName, clientId, newClientRole) - if err != nil { - return "", err - } else { - fmt.Println("Created client role", clientRoleName) + clientId, clientSecret := getClientInfo(realmName, clientName) + + newClientRole := Role{ + Name: clientRoleName, } + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles" + jsonValue, _ = json.Marshal(newClientRole) + statusCode, _ = sendRequest("POST", restUrl, jsonValue) - user, err := client.GetClientServiceAccount(ctx, token.AccessToken, realmName, clientId) + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/roles/" + clientRoleName + statusCode, data := sendRequest("GET", restUrl, nil) + roles := make(map[string]interface{}) + err := json.Unmarshal([]byte(data), &roles) if err != nil { fmt.Println(err) - panic("Oh no!, failed to get client user :(") - } else { - fmt.Println("Service Account user", *user.Username) } + roleId := fmt.Sprintf("%v", roles["id"]) - if authType == "client-x509" { - newUser := gocloak.User{ - ID: gocloak.StringP(realmName + "user"), - Username: gocloak.StringP(realmName + "user"), - Email: gocloak.StringP("client@mail.com"), - Enabled: gocloak.BoolP(true), - } - - realmUser, err := client.CreateUser(ctx, token.AccessToken, realmName, newUser) + if authType != "client-x509" { + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/service-account-user" + statusCode, data = sendRequest("GET", restUrl, nil) + serviceAccount := make(map[string]interface{}) + err = json.Unmarshal([]byte(data), &serviceAccount) if err != nil { fmt.Println(err) - panic("Oh no!, failed to create user :(") - } else { - fmt.Println("Created new user", realmUser) } + userId = fmt.Sprintf("%v", serviceAccount["id"]) } - clientRole, err := client.GetClientRole(ctx, token.AccessToken, realmName, clientId, clientRoleName) - if err != nil { - fmt.Println(err) - panic("Oh no!, failed to get client role :(") - } else { - fmt.Println("Retrieved client role", clientRoleName) + roleRepresentation := RoleRepresentation{ + ID: roleId, + Name: clientRoleName, + Composite: false, + ClientRole: true, } - clientRoles := []gocloak.Role{*clientRole} - err = client.AddClientRoleToUser(ctx, token.AccessToken, realmName, clientId, *user.ID, clientRoles) - if err != nil { - fmt.Println(err) - panic("Oh no!, failed to add client role to user :(") - } else { - fmt.Printf("Added %s to %s\n", *clientRole.Name, *user.Username) - } + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId + "/role-mappings/clients/" + clientId + jsonValue, _ = json.Marshal([]RoleRepresentation{roleRepresentation}) + statusCode, data = sendRequest("POST", restUrl, jsonValue) - clientroleMapper := gocloak.ProtocolMapperRepresentation{ - ID: gocloak.StringP("Client Role " + clientName + " Mapper"), - Name: gocloak.StringP("Client Role " + clientName + " Mapper"), - Protocol: gocloak.StringP("openid-connect"), - ProtocolMapper: gocloak.StringP("oidc-usermodel-client-role-mapper"), - Config: &map[string]string{ + clientroleMapper := ProtocolMapperRepresentation{ + Name: "Client Role " + clientName + " Mapper", + Protocol: "openid-connect", + ProtocolMapper: "oidc-usermodel-client-role-mapper", + Config: map[string]string{ "access.token.claim": "true", "aggregate.attrs": "", "claim.name": "clientRole", "id.token.claim": "true", "jsonType.label": "String", "multivalued": "true", - "userinfo.token.claim": "true", "usermodel.clientRoleMapping.clientId": clientName, + "userinfo.token.claim": "false", }, } - _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper) - if err != nil { - fmt.Println(err) - panic("Oh no!, failed to add client roleampper to client :(") + + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + "/protocol-mappers/models" + jsonValue, _ = json.Marshal(clientroleMapper) + statusCode, _ = sendRequest("POST", restUrl, jsonValue) + return clientSecret, nil +} + +func getClient(authType, clientName, flowId string) Client { + var newClient Client + newClient.ClientID = clientName + newClient.Enabled = true + newClient.DirectAccessGrantsEnabled = true + newClient.BearerOnly = false + newClient.PublicClient = false + newClient.ServiceAccountsEnabled = true + newClient.ClientAuthenticatorType = authType + newClient.DefaultClientScopes = []string{"email"} + if authType == "client-secret" { + newClient.Attributes = map[string]string{ + "use.refresh.tokens": "true", + "client_credentials.use_refresh_token": "true"} + } else if authType == "client-x509" { + newClient.Attributes = map[string]string{ + "use.refresh.tokens": "true", + "client_credentials.use_refresh_token": "true", + "x509.subjectdn": ".*client@mail.com.*", + "x509.allow.regex.pattern.comparison": "true"} + newClient.AuthenticationFlowBindingOverrides = map[string]string{ + "direct_grant": flowId} } else { - fmt.Println("Client rolemapper added to client") + jwksString, publicKey, kid := pemtojwks.CreateJWKS("/certs/client.crt") + newClient.Attributes = map[string]string{ + "token.endpoint.auth.signing.alg": "RS256", + "jwt.credential.public.key": publicKey, + "jwt.credential.kid": kid, + "use.jwks.url": "false", + "jwks.url": jwksString, + "use.refresh.tokens": "true", + "client_credentials.use_refresh_token": "true", + } } + return newClient +} - if authType == "client-x509" { - clientRole := *newClient.ClientID + "." + clientRoleName - - clientroleMapper := gocloak.ProtocolMapperRepresentation{ - ID: gocloak.StringP("Hardcoded " + clientName + " Mapper"), - Name: gocloak.StringP("Hardcoded " + clientName + " Mapper"), - Protocol: gocloak.StringP("openid-connect"), - ProtocolMapper: gocloak.StringP("oidc-hardcoded-role-mapper"), - Config: &map[string]string{ - "role": clientRole, - }, - } - _, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper) - if err != nil { - return "", err - } else { - fmt.Println("Created hardcoded-role-mapper for ", clientRole) - } +func getClientInfo(realmName, clientName string) (string, string) { + restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients?clientId=" + clientName + _, data := sendRequest("GET", restUrl, nil) + + clients := make([]map[string]interface{}, 0) + err := json.Unmarshal([]byte(data), &clients) + if err != nil { + fmt.Println(err) } + clientId := fmt.Sprintf("%v", clients[0]["id"]) + clientSecret := fmt.Sprintf("%v", clients[0]["secret"]) + return clientId, clientSecret +} - _, err = client.RegenerateClientSecret(ctx, token.AccessToken, realmName, clientId) +func createx509Flow(realmName string) { + var jsonValue []byte = []byte{} + authenticationFlowRepresentation := AuthenticationFlowRepresentation{ + Alias: flowAlias, + Description: "OpenID Connect Resource Owner Grant", + ProviderId: "basic-flow", + TopLevel: true, + BuiltIn: false, + AthenticationExecutions: []string{}, + } + restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows" + jsonValue, _ = json.Marshal(authenticationFlowRepresentation) + sendRequest("POST", restUrl, jsonValue) + + execution := Execution{ + Provider: "direct-grant-auth-x509-username", + } + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions/execution" + jsonValue, _ = json.Marshal(execution) + sendRequest("POST", restUrl, jsonValue) + + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowAlias + "/executions" + _, data := sendRequest("GET", restUrl, nil) + executionInfo := make([]map[string]interface{}, 0) + err := json.Unmarshal([]byte(data), &executionInfo) if err != nil { - return "", err + fmt.Println(err) } + executionId := fmt.Sprintf("%v", executionInfo[0]["id"]) + + authenticatorConfigRepresentation := AuthenticatorConfigRepresentation{ + Alias: flowAlias + " config", + Config: map[string]string{ + "x509-cert-auth.canonical-dn-enabled": "false", + "x509-cert-auth.serialnumber-hex-enabled": "false", + "x509-cert-auth.ocsp-fail-open": "false", + "x509-cert-auth.regular-expression": "(.*?)(?:$)", + "x509-cert-auth.crl-checking-enabled": "false", + "x509-cert-auth.certificate-policy-mode": "All", + "x509-cert-auth.timestamp-validation-enabled": "false", + "x509-cert-auth.confirmation-page-disallowed": "false", + "x509-cert-auth.mapper-selection": "Username or Email", + "x509-cert-auth.revalidate-certificate-enabled": "false", + "x509-cert-auth.crldp-checking-enabled": "false", + "x509-cert-auth.mapping-source-selection": "Subject's e-mail", + "x509-cert-auth.ocsp-checking-enabled": "false", + }, + } + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/executions/" + executionId + "/config" + jsonValue, _ = json.Marshal(authenticatorConfigRepresentation) + sendRequest("POST", restUrl, jsonValue) +} - cred, err := client.GetClientSecret(ctx, token.AccessToken, realmName, clientId) +func getFlowId(realmName string) string { + var flowId string = "" + restUrl := keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows" + _, data := sendRequest("GET", restUrl, nil) + flows := make([]map[string]interface{}, 0) + err := json.Unmarshal([]byte(data), &flows) if err != nil { - return "", err - } else { - fmt.Println("Generated client secret", *cred.Value) + fmt.Println(err) } - return *cred.Value, nil + for i, _ := range flows { + id := fmt.Sprintf("%v", flows[i]["id"]) + alias := fmt.Sprintf("%v", flows[i]["alias"]) + if alias == flowAlias { + flowId = id + } + } + return flowId } func connectToK8s() *kubernetes.Clientset { @@ -311,8 +440,8 @@ func connectToK8s() *kubernetes.Clientset { return clientset } -func createSecret(clientSecret, clientName, realmName, role, namespace string) { - secretName := role + "-secret" +func createSecret(clientSecret, clientName, realmName, namespace string) { + secretName := clientName + "-secret" clientset := connectToK8s() secrets := clientset.CoreV1().Secrets(namespace) secret := &corev1.Secret{ @@ -336,52 +465,45 @@ func createSecret(clientSecret, clientName, realmName, role, namespace string) { } func remove(realmName, clientName string) { - adminClient := gocloak.NewClient("http://keycloak.default:8080") - ctx := context.Background() - token, err := adminClient.LoginAdmin(ctx, "admin", "admin", "master") - if err != nil { - fmt.Println(err) - } + getAdminToken() + clientId, _ := getClientInfo(realmName, clientName) - clients, err := adminClient.GetClients(ctx, token.AccessToken, realmName, - gocloak.GetClientsParams{ - ClientID: gocloak.StringP(clientName), - }, - ) - if err != nil { - panic("List clients failed:" + err.Error()) + restUrl := keycloakUrl + "/admin/realms/" + realmName + "/clients/" + clientId + sendRequest("DELETE", restUrl, nil) + + var userId string = "" + userName := realmName + "user" + userId = getUserId(realmName, userName) + if userId != "" { + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/users/" + userId + sendRequest("DELETE", restUrl, nil) } - for _, client := range clients { - err = adminClient.DeleteClient(ctx, token.AccessToken, realmName, *client.ID) - if err != nil { - fmt.Println(err) - } else { - fmt.Println("Deleted client ", clientName) - } + + flowId := getFlowId(realmName) + if flowId != "" { + restUrl = keycloakUrl + "/admin/realms/" + realmName + "/authentication/flows/" + flowId + sendRequest("DELETE", restUrl, nil) } +} - userName := realmName + "user" - users, err := adminClient.GetUsers(ctx, token.AccessToken, realmName, - gocloak.GetUsersParams{ - Username: gocloak.StringP(userName), - }) +func getUserId(realmName, userName string) string { + var userId string = "" + restUrl := keycloakUrl + "/admin/realms/" + realmName + "/users?username=demouser" + _, data := sendRequest("GET", restUrl, nil) + user := make([]map[string]interface{}, 0) + err := json.Unmarshal([]byte(data), &user) if err != nil { - panic("List users failed:" + err.Error()) + fmt.Println(err) } - for _, user := range users { - err = adminClient.DeleteUser(ctx, token.AccessToken, realmName, *user.ID) - if err != nil { - fmt.Println(err) - } else { - fmt.Println("Deleted user ", userName) - } + if len(user) > 0 { + userId = fmt.Sprintf("%v", user[0]["id"]) } - + return userId } -func removeSecret(namespace, role string) { +func removeSecret(namespace, clientName string) { clientset := connectToK8s() - secretName := role + "-secret" + secretName := clientName + "-secret" secrets := clientset.CoreV1().Secrets(namespace) err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{}) if err != nil { diff --git a/service-exposure/templates/AuthorizationPolicy-template.txt b/service-exposure/templates/AuthorizationPolicy-template.txt index 0899475c..79c90e0d 100644 --- a/service-exposure/templates/AuthorizationPolicy-template.txt +++ b/service-exposure/templates/AuthorizationPolicy-template.txt @@ -1,6 +1,6 @@ # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ spec: rules: - from: - source: - requestPrincipals: ["http://istio-ingressgateway.istio-system:80/auth/realms/{{.Realm}}/"] + requestPrincipals: ["http://istio-ingressgateway.istio-system:80/auth/realms/{{.Realm}}/", "https://keycloak:8443/realms/{{.Realm}}"] - to: - operation: methods: ["{{.Method}}"] diff --git a/service-exposure/templates/RequestAuthentication-template.txt b/service-exposure/templates/RequestAuthentication-template.txt index 5fbdbbbc..48f651a8 100644 --- a/service-exposure/templates/RequestAuthentication-template.txt +++ b/service-exposure/templates/RequestAuthentication-template.txt @@ -1,6 +1,6 @@ # # ============LICENSE_START======================================================= -# Copyright (C) 2022 Nordix Foundation. +# Copyright (C) 2022-2023 Nordix Foundation. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,3 +29,5 @@ spec: jwtRules: - issuer: "http://istio-ingressgateway.istio-system:80/auth/realms/{{.Realm}}" jwksUri: "http://keycloak.default:8080/auth/realms/{{.Realm}}/protocol/openid-connect/certs" + - issuer: "https://keycloak:8443/realms/{{.Realm}}" + jwksUri: "http://keycloak.default:8080/realms/{{.Realm}}/protocol/openid-connect/certs" diff --git a/service-exposure/utils/pemtojwks/pemtojwks.go b/service-exposure/utils/pemtojwks/pemtojwks.go index 6317843d..d232d781 100644 --- a/service-exposure/utils/pemtojwks/pemtojwks.go +++ b/service-exposure/utils/pemtojwks/pemtojwks.go @@ -2,7 +2,7 @@ // ========================LICENSE_START================================= // O-RAN-SC // %% -// Copyright (C) 2022: Nordix Foundation +// Copyright (C) 2022-2023: Nordix Foundation // %% // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ // limitations under the License. // ========================LICENSE_END=================================== // -package pemtojwks +package pemtojwks import ( "crypto/rsa" @@ -38,6 +38,7 @@ type Jwks struct { type Key struct { Kid string `json:"kid,omitempty"` Kty string `json:"kty"` + Alg string `json:"alg"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` @@ -93,27 +94,35 @@ func getPublicKeyFromCert(cert_bytes []byte) *rsa.PublicKey { cert, _ = x509.ParseCertificate(block.Bytes) rsaPublicKey := cert.PublicKey.(*rsa.PublicKey) - return rsaPublicKey + return rsaPublicKey } -func CreateJWKS(certFile string) string { +func CreateJWKS(certFile string) (string, string, string) { var publicKey *rsa.PublicKey + var kid string = "SIGNING_KEY" cert, err := ioutil.ReadFile(certFile) if err != nil { fmt.Println(err) } publicKey = getPublicKeyFromCert(cert) + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + fmt.Println(err) + } + publicKeyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: publicKeyBytes}) + block, _ := pem.Decode(publicKeyPem) + publicKeyString := base64.StdEncoding.EncodeToString(block.Bytes) certificate := getCert(cert) // generate fingerprint with sha1 // you can also use md5, sha256, etc. fingerprint := sha1.Sum(certificate.Raw) - jwksKey := Key{ - Kid: "SIGNING_KEY", + Kid: kid, Kty: "RSA", + Alg: "RS256", Use: "sig", N: base64.RawStdEncoding.EncodeToString(publicKey.N.Bytes()), E: base64.RawStdEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()), @@ -126,8 +135,7 @@ func CreateJWKS(certFile string) string { jwksJson, err := json.Marshal(jwks) if err != nil { fmt.Println(err) - return err.Error() } - return string(jwksJson) + return string(jwksJson), publicKeyString, kid } -- 2.16.6