Skip to content
Draft
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
12 changes: 10 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM icr.io/appcafe/open-liberty:kernel-slim-java11-openj9-ubi-minimal
ARG VERSION=1.0
ARG REVISION=SNAPSHOT
ARG SKIP_LINPERF=false

LABEL \
org.opencontainers.image.authors="Alasdair Nottingham" \
Expand All @@ -15,13 +16,20 @@ LABEL \
summary="Sample app running on Open Liberty that uses Eclipse MicroProfile" \
description="This image contains a sample application that displays the Java system properties and demonstrates MicroProfile Config, Health and Metrics."

# Install required packages to run linperf.sh
# Install required packages for running the Liberty MustGather (linperf.sh) script.
USER 0
RUN command -v yum && pkgcmd=yum || pkgcmd=microdnf && ($pkgcmd update -y && $pkgcmd install -y procps-ng net-tools ncurses hostname)
RUN if [ "$SKIP_LINPERF" != "true" ]; then \
PKG_MGR=$(command -v dnf || command -v microdnf) && \
$PKG_MGR install -y procps-ng net-tools ncurses hostname && \
$PKG_MGR clean all; \
fi
USER 1001

COPY --chown=1001:0 src/main/liberty/config/ /config/

# Bundle the dev SSO for mpMetrics. You can override at deploy time by mounting a different sso.xml
COPY --chown=1001:0 dev/sso.xml /config/configDropins/overrides/sso.xml

RUN features.sh

COPY --chown=1001:0 target/*.war /config/apps/
Expand Down
68 changes: 68 additions & 0 deletions dev/getting-started-app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
# Development OpenLibertyApplication: Liberty app + Keycloak sidecar
# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080.
#
# Prerequisites:
# oc create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json
# oc apply -f dev/getting-started-app.yaml
#
# To hit the /metrics endpoint run:
# ./dev/metrics.sh
#
# In production: remove sidecarContainers, and provide a Secret
# containing the real sso.xml mounted at /config/configDropins/overrides/sso.xml.
apiVersion: apps.openliberty.io/v1
kind: OpenLibertyApplication
metadata:
name: getting-started
spec:
applicationImage: icr.io/appcafe/open-liberty/samples/getting-started:latest
replicas: 1

service:
port: 9443

# Dev Keycloak sidecar — shares localhost with the Liberty container
sidecarContainers:
- name: keycloak
image: quay.io/keycloak/keycloak:25.0
args: ["start-dev", "--import-realm"]
env:
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
value: admin
volumeMounts:
- name: realm-config
mountPath: /opt/keycloak/data/import
ports:
- containerPort: 8080

# Mount the dev sso.xml; in production replace with a Secret mount pointing at your real IdP
volumeMounts:
- name: sso-override
mountPath: /config/configDropins/overrides/sso.xml
subPath: sso.xml

volumes:
- name: realm-config
configMap:
name: keycloak-realm
- name: sso-override
configMap:
name: liberty-sso-dev
---
# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost
apiVersion: v1
kind: ConfigMap
metadata:
name: liberty-sso-dev
data:
sso.xml: |
<server>
<variable name="MP_JWT_ISSUER" defaultValue="http://localhost:8080/realms/liberty"/>
<variable name="MP_JWT_JWKS_URI" defaultValue="http://localhost:8080/realms/liberty/protocol/openid-connect/certs"/>
<mpJwt id="mpJwtConfig"
issuer="${MP_JWT_ISSUER}"
jwksUri="${MP_JWT_JWKS_URI}"/>
</server>
44 changes: 44 additions & 0 deletions dev/keycloak-realm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"realm": "liberty",
"enabled": true,
"requiredCredentials": ["password"],
"attributes": {
"userProfileEnabled": "false"
},
"requiredActions": [
{
"alias": "VERIFY_PROFILE",
"name": "Verify Profile",
"providerId": "VERIFY_PROFILE",
"enabled": false,
"defaultAction": false,
"priority": 90,
"config": {}
}
],
"clients": [
{
"clientId": "metrics-client",
"enabled": true,
"publicClient": false,
"secret": "metrics-secret",
"directAccessGrantsEnabled": true,
"defaultClientScopes": ["profile", "email"]
}
],
"users": [
{
"username": "metrics",
"enabled": true,
"emailVerified": true,
"requiredActions": [],
"credentials": [
{
"type": "password",
"value": "metrics",
"temporary": false
}
]
}
]
}
75 changes: 75 additions & 0 deletions dev/metrics.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# dev/metrics.sh — fetch the mpMetrics endpoint from the getting-started pod
#
# Usage:
# ./dev/metrics.sh # all metrics (default)
# ./dev/metrics.sh /metrics/base # base metrics
# ./dev/metrics.sh /metrics/application # application metrics

set -uo pipefail # no -e: port-forward drops are handled explicitly

METRICS_PATH="${1:-/metrics}"
LABEL="app.kubernetes.io/name=getting-started"
LIBERTY_PORT=9443
KEYCLOAK_PORT=8080
PF_PID=""

cleanup() { [[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null; }
trap cleanup EXIT

start_portforward() {
[[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null
oc port-forward "$POD" "${LIBERTY_PORT}:${LIBERTY_PORT}" "${KEYCLOAK_PORT}:${KEYCLOAK_PORT}" \
2>/dev/null &
PF_PID=$!
}

# ── 1. Resolve pod name ──────────────────────────────────────────────────────
POD=$(oc get pod -l "$LABEL" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [[ -z "$POD" ]]; then
echo "ERROR: no pod found with label $LABEL" >&2
exit 1
fi
echo "Using pod: $POD"

# ── 2. Wait for Keycloak container to be ready ───────────────────────────────
echo "Waiting for Keycloak container to be ready..."
oc wait pod "$POD" --for=condition=Ready --timeout=300s 2>/dev/null || true

# ── 3. Port-forward and wait for Keycloak OIDC endpoint ─────────────────────
echo "Waiting for Keycloak realm..."
start_portforward
for i in $(seq 1 90); do
# Restart port-forward if it died
if ! kill -0 "$PF_PID" 2>/dev/null; then
sleep 3
start_portforward
fi
curl -sf "http://localhost:${KEYCLOAK_PORT}/realms/liberty/.well-known/openid-configuration" \
-o /dev/null 2>/dev/null && break
sleep 3
done

# ── 4. Obtain JWT from Keycloak ──────────────────────────────────────────────
echo "Obtaining token..."
TOKEN=$(curl -sf -X POST \
"http://localhost:${KEYCLOAK_PORT}/realms/liberty/protocol/openid-connect/token" \
-d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
| jq -r .access_token)

if [[ -z "$TOKEN" ]] || [[ "$TOKEN" == "null" ]]; then
echo "ERROR: failed to obtain token — check Keycloak logs with:" >&2
echo " oc logs $POD -c keycloak | grep -i error" >&2
exit 1
fi
echo "Token obtained."

# ── 5. Fetch metrics ─────────────────────────────────────────────────────────
echo ""
echo "GET https://localhost:${LIBERTY_PORT}${METRICS_PATH}"
echo "────────────────────────────────────────────────────"
HTTP_CODE=$(curl -sk -o /tmp/metrics_response.txt -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://localhost:${LIBERTY_PORT}${METRICS_PATH}")
echo "HTTP $HTTP_CODE"
cat /tmp/metrics_response.txt
20 changes: 20 additions & 0 deletions dev/sso.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!--
Development SSO config — auto-loaded from /config/configDropins/overrides/ by Liberty.
No <include> needed in server.xml.

In production, replace this file by mounting a different sso.xml at the same path.
Do NOT commit real issuer URIs or secrets here — inject via environment variables.
-->
<server>
<!--
MP_JWT_ISSUER = token issuer URI, must match the 'iss' claim in incoming JWTs
MP_JWT_JWKS_URI = JWKS endpoint Liberty uses to verify token signatures
Defaults below point at the Keycloak dev sidecar.
-->
<variable name="MP_JWT_ISSUER" defaultValue="http://keycloak:8080/realms/liberty"/>
<variable name="MP_JWT_JWKS_URI" defaultValue="http://keycloak:8080/realms/liberty/protocol/openid-connect/certs"/>

<mpJwt id="mpJwtConfig"
issuer="${MP_JWT_ISSUER}"
jwksUri="${MP_JWT_JWKS_URI}"/>
</server>
37 changes: 37 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
services:

keycloak:
image: quay.io/keycloak/keycloak:25.0
command: start-dev --import-realm
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
volumes:
- ./dev/keycloak-realm.json:/opt/keycloak/data/import/realm.json:ro
ports:
- "8080:8080"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration || exit 1"]
interval: 10s
timeout: 5s
retries: 12

app:
build: .
volumes:
# Mount the dev SSO include file into the Liberty config includes directory
- ./dev/sso.xml:/config/configDropins/overrides/sso.xml:ro
ports:
- "9443:9443"
depends_on:
keycloak:
condition: service_healthy

# To get a token and hit /metrics:
#
# TOKEN=$(curl -s -X POST \
# http://localhost:8080/realms/liberty/protocol/openid-connect/token \
# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
# | jq -r .access_token)
#
# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics
12 changes: 9 additions & 3 deletions src/main/liberty/config/server.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@
<feature>mpMetrics-5.1</feature>
<feature>mpHealth-4.0</feature>
<feature>mpConfig-3.1</feature>
<!-- JWT Bearer auth for /metrics -->
<feature>mpJwt-2.1</feature>
</featureManager>

<mpMetrics authentication="false" />
<httpOptions removeServerHeader="true" />
<webContainer disableXPoweredBy="true" />
<webAppSecurity ssoRequiresSSL="true" httpOnlyCookies="true" />

<!-- mpMetrics: authentication delegated to mpJwt (see /config/configDropins/overrides/sso.xml) -->
<mpMetrics authentication="true"/>

<variable name="default.http.port" defaultValue="9080"/>
<variable name="default.https.port" defaultValue="9443"/>
<variable name="app.context.root" defaultValue="/"/>

<httpEndpoint host="*" httpPort="${default.http.port}" httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
<httpEndpoint host="*" httpPort="-1" httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>

<webApplication location="io.openliberty.sample.getting.started.war" contextRoot="${app.context.root}"/>
</server>