Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve k8s keycloak bootstrapping script #1278

Merged
merged 10 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ object EndpointOutputs {

val basicFailures: EndpointOutput[ErrorResponse] = basicFailuresWith()

val basicFailuresAndForbidden = basicFailuresWith(FailureVariant.forbidden)
val basicFailuresAndForbidden = basicFailuresWith(FailureVariant.unauthorized, FailureVariant.forbidden)

val basicFailuresAndNotFound = basicFailuresWith(FailureVariant.notFound)

val basicFailureAndNotFoundAndForbidden = basicFailuresWith(FailureVariant.notFound, FailureVariant.forbidden)
val basicFailureAndNotFoundAndForbidden =
basicFailuresWith(FailureVariant.notFound, FailureVariant.unauthorized, FailureVariant.forbidden)

object FailureVariant {
val badRequest = oneOfVariantValueMatcher(
Expand Down Expand Up @@ -57,6 +58,11 @@ object EndpointOutputs {
StatusCode.Forbidden,
jsonBody[ErrorResponse].description("Forbidden")
)(statusCodeMatcher(StatusCode.Forbidden))

val unauthorized = oneOfVariantValueMatcher(
StatusCode.Unauthorized,
jsonBody[ErrorResponse].description("Unauthorized")
)(statusCodeMatcher(StatusCode.Unauthorized))
}

}
1 change: 1 addition & 0 deletions infrastructure/charts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/charts/*.tgz
183 changes: 101 additions & 82 deletions infrastructure/charts/agent/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,92 +5,111 @@ metadata:
labels:
{{- include "labels.common" . | nindent 4 }}
data:
init.sh: |
#!/usr/bin/env bash
init.ts: |
const KEYCLOAK_BASE_URL = process.env.KEYCLOAK_BASE_URL!;
const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER!;
const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD!;
const REALM_NAME = process.env.REALM_NAME!;
const CLOUD_AGENT_CLIENT_ID = process.env.CLOUD_AGENT_CLIENT_ID!;
const CLOUD_AGENT_CLIENT_SECRET = process.env.CLOUD_AGENT_CLIENT_SECRET!;

set -e
set -u

KEYCLOAK_BASE_URL=$KEYCLOAK_BASE_URL
KEYCLOAK_ADMIN_USER=$KEYCLOAK_ADMIN_USER
KEYCLOAK_ADMIN_PASSWORD=$KEYCLOAK_ADMIN_PASSWORD
REALM_NAME=$REALM_NAME
CLOUD_AGENT_CLIENT_ID=$CLOUD_AGENT_CLIENT_ID
CLOUD_AGENT_CLIENT_SECRET=$CLOUD_AGENT_CLIENT_SECRET

function get_admin_token() {
local response=$(
curl --request POST "$KEYCLOAK_BASE_URL/realms/master/protocol/openid-connect/token" \
--fail -s -v \
--data-urlencode "grant_type=password" \
--data-urlencode "client_id=admin-cli" \
--data-urlencode "username=$KEYCLOAK_ADMIN_USER" \
--data-urlencode "password=$KEYCLOAK_ADMIN_PASSWORD"
)
local access_token=$(echo $response | jq -r '.access_token')
echo $access_token
}

function is_client_exists() {
local access_token=$1
local client_id=$2

local http_status=$(
curl --request GET "$KEYCLOAK_BASE_URL/admin/realms/$REALM_NAME/clients/$client_id" \
-s -w "%{http_code}" \
-o /dev/null \
-H "Authorization: Bearer $access_token"
)

if [ $http_status == 200 ]; then
echo "true"
else
echo "false"
fi
async function getAdminToken(): Promise<string> {
const req = new Request(`${KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
"grant_type": "password",
"client_id": "admin-cli",
"username": KEYCLOAK_ADMIN_USER,
"password": KEYCLOAK_ADMIN_PASSWORD,
})
});
const resp = await fetch(req);
const body = await resp.json();
if (resp.status !== 200) {
throw new Error("Response did not return success status code." +
` Status: ${resp.status}, Body: ${JSON.stringify(body)}`
);
}
return body["access_token"];
}

function create_client() {
local access_token=$1
local client_id=$2
local client_secret=$3

curl --request POST "$KEYCLOAK_BASE_URL/admin/realms/$REALM_NAME/clients" \
--fail -s \
-H "Authorization: Bearer $access_token" \
-H "Content-Type: application/json" \
--data-raw "{
\"id\": \"$client_id\",
\"directAccessGrantsEnabled\": true,
\"authorizationServicesEnabled\": true,
\"serviceAccountsEnabled\": true,
\"secret\": \"$client_secret\"
}"
async function createRealm(accessToken: string) {
console.log(`Creating realm ${REALM_NAME} ...`);
const req = new Request(`${KEYCLOAK_BASE_URL}/admin/realms`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
},
body: JSON.stringify({
"realm": REALM_NAME,
"enabled": true,
})
});
const resp = await fetch(req);
const resp_body = await resp.json();
switch (resp.status) {
case 201:
console.log(`Realm ${REALM_NAME} created.`);
break;
case 409:
console.log(`Realm ${REALM_NAME} already exists.`);
break;
default:
throw new Error("Response did not return success status code." +
` Status: ${resp.status}, Body: ${JSON.stringify(resp_body)}`
);
}
}

echo "Getting admin access token ..."
ADMIN_ACCESS_TOKEN=$(get_admin_token)

CLIENT_EXIST=$(is_client_exists $ADMIN_ACCESS_TOKEN $CLOUD_AGENT_CLIENT_ID)
if [ $CLIENT_EXIST == "false" ]; then
echo "Creating a new $CLOUD_AGENT_CLIENT_ID client ..."
create_client $ADMIN_ACCESS_TOKEN $CLOUD_AGENT_CLIENT_ID $CLOUD_AGENT_CLIENT_SECRET
fi
async function createClient(accessToken: string, clientId: string, clientSecret?: string) {
console.log(`Creating client ${clientId} ...`);
let req_body = {};
if (clientSecret) {
req_body = {
"id": clientId,
"directAccessGrantsEnabled": true,
"authorizationServicesEnabled": true,
"serviceAccountsEnabled": true,
"secret": clientSecret
};
} else {
// public client is created if client secret is not provided
req_body = {
"id": clientId,
"publicClient": true,
"consentRequired": true,
"redirectUris": ["*"]
};
}

{{- if .Values.keycloak.enabled }}

---

apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "cloud-agent.name" . }}-realm-import
labels:
{{- include "labels.common" . | nindent 4}}
data:
{{ include "cloud-agent.name" . }}.json: |
{
"realm": {{ .Values.server.keycloak.realm | quote }},
"enabled": true
const req = new Request(`${KEYCLOAK_BASE_URL}/admin/realms/${REALM_NAME}/clients`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
},
body: JSON.stringify(req_body)
});
const resp = await fetch(req);
const resp_body = await resp.json();
switch (resp.status) {
case 201:
console.log(`Client ${clientId} created.`);
break;
case 409:
console.log(`Client ${clientId} already exists.`);
break;
default:
throw new Error("Response did not return success status code." +
` Status: ${resp.status}, Body: ${JSON.stringify(resp_body)}`
);
}
}

{{- end }}
(async () => {
console.log("Getting admin access token ...");
const adminToken = await getAdminToken();
await createRealm(adminToken);
await createClient(adminToken, CLOUD_AGENT_CLIENT_ID, CLOUD_AGENT_CLIENT_SECRET);
})();
15 changes: 6 additions & 9 deletions infrastructure/charts/agent/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ spec:
image: busybox
command: ['sh', '-c', "until nc -z {{ .Values.database.postgres.managingTeam }}-{{ include "cloud-agent.name" . }}-postgres-cluster.{{ .Release.Namespace }} 5432; do echo waiting for postgress-operator; sleep 2; done;"]
{{- if .Values.server.keycloak.enabled }}
- name: wait-keycloak-ready
image: badouralix/curl-jq:ubuntu
command: ['/bin/bash', '-c', 'until curl http://{{ .Release.Name }}-keycloak/health/ready; do sleep 2; done && echo "Keycloak is ready."']
{{- if .Values.server.keycloak.bootstrap }}
- name: keycloak-bootstrap
image: badouralix/curl-jq:ubuntu
command: ['/bin/bash', '-c', '/scripts/init.sh']
image: oven/bun:1
command: ['bun', 'run', '/scripts/init.ts']
env:
- name: KEYCLOAK_BASE_URL
value: http://{{ .Release.Name }}-keycloak
value: {{ .Values.server.keycloak.url }}
- name: KEYCLOAK_ADMIN_USER
value: {{ .Values.server.keycloak.admin.username }}
- name: KEYCLOAK_ADMIN_PASSWORD
Expand Down Expand Up @@ -202,7 +199,7 @@ spec:
- name: KEYCLOAK_ENABLED
value: "true"
- name: KEYCLOAK_URL
value: http://{{ .Release.Name }}-keycloak
value: {{ .Values.server.keycloak.url }}
- name: KEYCLOAK_REALM
value: {{ .Values.server.keycloak.realm }}
- name: KEYCLOAK_CLIENT_ID
Expand All @@ -222,8 +219,8 @@ spec:
name: keycloak-bootstrap-script
defaultMode: 0500
items:
- key: "init.sh"
path: "init.sh"
- key: "init.ts"
path: "init.ts"
{{- end }}
affinity:
{{- toYaml .Values.affinity | nindent 8 }}
Expand Down
16 changes: 3 additions & 13 deletions infrastructure/charts/agent/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ server:
useVault: true
keycloak:
enabled: false
url: http://agent-keycloak
realm: prism-agent
bootstrap: true
admin:
Expand All @@ -59,7 +60,7 @@ server:
name: keycloak-admin-secret
key: password
client:
clientId: prism-agent
clientId: cloud-agent
clientSecret:
secretKeyRef:
name: agent-keycloak-client-secret
Expand Down Expand Up @@ -136,7 +137,7 @@ vault:
keycloak:
enabled: false
# --hostname-url should be the frontend url that user will be logging in with keycloak
extraStartupArgs: "--hostname-url=http://localhost:8080 --import-realm --features=declarative-user-profile"
extraStartupArgs: "--hostname-url=http://localhost:8080 --features=declarative-user-profile"
tls:
enabled: true
autoGenerated: true
Expand All @@ -154,17 +155,6 @@ keycloak:
port: "5432"
user: keycloak-admin
database: keycloak
extraVolumes:
- name: cloud-agent-realm-import-volume
configMap:
name: cloud-agent-realm-import
items:
- key: cloud-agent.json
path: cloud-agent.json
extraVolumeMounts:
- name: cloud-agent-realm-import-volume
mountPath: /opt/bitnami/keycloak/data/import
readOnly: true

# It is configured for deployment and postgresql objects of cloud-agent
affinity:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ import org.hyperledger.identus.pollux.core.service.serdes.{AnoncredCredentialPro
import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, PresentationCompact}
import org.hyperledger.identus.pollux.vc.jwt.{Issuer, PresentationPayload, W3cCredentialPayload}
import org.hyperledger.identus.shared.models.*
import zio.{mock, IO, URLayer, ZIO, ZLayer}
import zio.{mock, IO, UIO, URLayer, ZIO, ZLayer}
import zio.json.*
import zio.mock.{Mock, Proxy}
import zio.UIO

import java.time.Instant
import java.util.UUID
Expand Down
Loading