From f87352728814012fef57fb43943e86b3c4661133 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Thu, 16 Nov 2023 17:06:21 +0200 Subject: [PATCH 01/15] remove unused property. --- src/main/resources/application-prod.properties | 1 - src/main/resources/application.properties | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 8cd61ecf..6b164c0f 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -9,7 +9,6 @@ server.port=8080 # # Issuer options # -issuer.clientId=pid-issuer issuer.publicUrl=http://localhost:${server.port} issuer.authorizationServer=https://keycloak-eudi.netcompany-intrasoft.com/realms/pid-issuer-realm issuer.authorizationServer.introspection=${issuer.authorizationServer}/protocol/openid-connect/token/introspect diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a6abdf33..501b962e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,7 +9,6 @@ server.port=8080 # # Issuer options # -issuer.clientId=pid-issuer issuer.publicUrl=http://localhost:${server.port} issuer.authorizationServer=https://localhost/idp/realms/pid-issuer-realm issuer.authorizationServer.introspection=${issuer.authorizationServer}/protocol/openid-connect/token/introspect From d480f12746ec5c78e484db604b46232dee92040d Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 11:30:00 +0200 Subject: [PATCH 02/15] Add the ability to expose the Authorization Server using a different URL in Credential Issuer Metadata. --- .../eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index 1060a02b..bb3adc66 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -133,7 +133,8 @@ fun beans(clock: Clock) = beans { deferredCredentialEndpoint = env.readRequiredUrl("issuer.publicUrl").run { HttpsUrl.unsafe("${this.value}${WalletApi.DEFERRED_ENDPOINT}") }, - authorizationServer = env.readRequiredUrl("issuer.authorizationServer"), + authorizationServer = env.readOptionalUrl("issuer.authorizationServer.publicUrl") + ?: env.readRequiredUrl("issuer.authorizationServer"), credentialResponseEncryption = env.credentialResponseEncryption(), specificCredentialIssuers = buildList { @@ -308,6 +309,9 @@ private fun Environment.readRequiredUrl(key: String): HttpsUrl = HttpsUrl.of(url) ?: HttpsUrl.unsafe(url) } +private fun Environment.readOptionalUrl(key: String): HttpsUrl? = + getProperty(key)?.let { HttpsUrl.of(it) ?: HttpsUrl.unsafe(it) } + private fun Environment.readNonEmptySet(key: String, f: (String) -> T?): NonEmptySet { val nonEmptySet = getRequiredProperty>(key) .mapNotNull(f) From a0d0a63610fb228367ab840e85022c2d760e0a1e Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 11:36:12 +0200 Subject: [PATCH 03/15] Add and enable Spring Actuator's Health endpoint. --- build.gradle.kts | 3 +++ .../kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 5d6ca5c8..404b631d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,9 @@ dependencies { implementation(libs.bouncy.castle) { because("To support X509 certificates parsing") } + implementation("org.springframework.boot:spring-boot-starter-actuator") { + because("To add Actuator's health endpoint") + } testImplementation(kotlin("test")) testImplementation(libs.kotlinx.coroutines.test) testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index bb3adc66..563111a1 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -247,9 +247,12 @@ fun beans(clock: Clock) = beans { authorize(MetaDataApi.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER, permitAll) authorize(MetaDataApi.WELL_KNOWN_JWKS, permitAll) authorize(IssuerApi.CREDENTIALS_OFFER, permitAll) + authorize("/actuator/health", permitAll) authorize(anyExchange, denyAll) } + anonymous {} + csrf { disable() } From 1069a2e7ca5d3b9aac8d8a494e1ae35b0d22f8ff Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 11:39:52 +0200 Subject: [PATCH 04/15] Cleanup build.gradle.kts --- build.gradle.kts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 404b631d..8a8b8689 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.springframework.boot.gradle.tasks.bundling.BootBuildImage import java.net.URI -import kotlin.jvm.optionals.getOrNull plugins { base @@ -64,25 +63,17 @@ dependencies { } java { - val javaVersion = getVersionFromCatalog("java") + val javaVersion = libs.versions.java.get() sourceCompatibility = JavaVersion.toVersion(javaVersion) } kotlin { jvmToolchain { - val javaVersion = getVersionFromCatalog("java") + val javaVersion = libs.versions.java.get() languageVersion.set(JavaLanguageVersion.of(javaVersion)) } } -fun getVersionFromCatalog(lookup: String): String { - val versionCatalog: VersionCatalog = extensions.getByType().named("libs") - return versionCatalog - .findVersion(lookup) - .getOrNull() - ?.requiredVersion - ?: throw GradleException("Version '$lookup' is not specified in the version catalog") -} tasks.withType().configureEach { kotlinOptions { @@ -90,6 +81,7 @@ tasks.withType().configureEach { freeCompilerArgs += "-Xjsr305=strict" } } + testing { suites { val test by getting(JvmTestSuite::class) { @@ -119,7 +111,7 @@ tasks.named("bootBuildImage") { } spotless { - val ktlintVersion = getVersionFromCatalog("ktlintVersion") + val ktlintVersion = libs.versions.ktlintVersion.get() kotlin { ktlint(ktlintVersion) licenseHeaderFile("FileHeader.txt") @@ -128,7 +120,3 @@ spotless { ktlint(ktlintVersion) } } - -// tasks.withType { -// useJUnitPlatform() -// } From e98ee33345320f81243cc4462978c8fb6206e459 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 12:10:53 +0200 Subject: [PATCH 05/15] Add pid-issuer to docker-compose. --- keycloak/docker-compose.yaml | 28 ++++++++++++++++++++++++++-- keycloak/haproxy/haproxy.conf | 8 ++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/keycloak/docker-compose.yaml b/keycloak/docker-compose.yaml index ed350bec..ad967819 100644 --- a/keycloak/docker-compose.yaml +++ b/keycloak/docker-compose.yaml @@ -16,8 +16,6 @@ services: - postgres_data:/var/lib/postgresql/data networks: - default - ports: - - "0.0.0.0:5432:5432" healthcheck: test: "exit 0" environment: @@ -62,6 +60,30 @@ services: networks: - default + pid-issuer: + image: ghcr.io/eu-digital-identity-wallet/eudi-srv-pid-issuer:edge + pull_policy: always + container_name: pid-issuer + depends_on: + keycloak: + condition: service_healthy + command: + - --spring.webflux.base-path=/pid-issuer + - --issuer.publicUrl=https://localhost/pid-issuer + - --issuer.authorizationServer.publicUrl=https://localhost/idp/realms/pid-issuer-realm + - --issuer.authorizationServer=http://keycloak:8080/idp/realms/pid-issuer-realm + - --issuer.authorizationServer.introspection=http://keycloak:8080/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect + - --issuer.authorizationServer.userinfo=http://keycloak:8080/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo + - --issuer.pid.mso_mdoc.enabled=true + - --issuer.pid.mso_mdoc.encoderUrl=https://preprod.issuer.eudiw.dev/formatter/cbor + - --issuer.pid.sd_jwt_vc.enabled=true + - --issuer.pid.sd_jwt_vc.deferred=true + - --issuer.pid.issuingCountry=FC + - --spring.security.oauth2.resourceserver.opaquetoken.client-id=pid-issuer-srv + - --spring.security.oauth2.resourceserver.opaquetoken.client-secret=zIKAV9DIIIaJCzHCVBPlySgU8KgY68U2 + networks: + - default + haproxy: image: haproxy:2.8.3 container_name: haproxy @@ -71,6 +93,8 @@ services: depends_on: keycloak: condition: service_healthy + pid-issuer: + condition: service_started volumes: - ./haproxy/haproxy.conf:/usr/local/etc/haproxy/haproxy.cfg - ./haproxy/certs/:/etc/ssl/certs/ diff --git a/keycloak/haproxy/haproxy.conf b/keycloak/haproxy/haproxy.conf index 0f3b03df..ab3f6565 100755 --- a/keycloak/haproxy/haproxy.conf +++ b/keycloak/haproxy/haproxy.conf @@ -20,10 +20,12 @@ defaults frontend all_http_frontend bind 0.0.0.0:80 use_backend keycloak-backend if { path_beg /idp } + use_backend pid-issuer-backend if { path_beg /pid-issuer } frontend all_https_frontend bind 0.0.0.0:443 ssl crt /etc/ssl/certs/localhost.tls.pem use_backend keycloak-backend if { path_beg /idp } + use_backend pid-issuer-backend if { path_beg /pid-issuer } backend keycloak-backend balance roundrobin @@ -31,5 +33,11 @@ backend keycloak-backend option forwarded proto host by by_port for server server1 keycloak:8080 cookie server1 +backend pid-issuer-backend + balance roundrobin + cookie SERVERUSED insert indirect nocache + option forwarded proto host by by_port for + server server1 pid-issuer:8080 cookie server1 + backend no-match http-request deny deny_status 404 From af923e565b9fc6cc64b76c84f37995ad6ebff0c0 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 14:37:28 +0200 Subject: [PATCH 06/15] Enable TLS termination in Keycloak to allow introspection and userinfo endpoint to work without issues. --- keycloak/docker-compose.yaml | 11 +++-- keycloak/keycloak-certs/keycloak.tls.crt | 28 +++++++++++++ keycloak/keycloak-certs/keycloak.tls.key | 52 ++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 keycloak/keycloak-certs/keycloak.tls.crt create mode 100644 keycloak/keycloak-certs/keycloak.tls.key diff --git a/keycloak/docker-compose.yaml b/keycloak/docker-compose.yaml index ad967819..6e6c8d53 100644 --- a/keycloak/docker-compose.yaml +++ b/keycloak/docker-compose.yaml @@ -32,7 +32,11 @@ services: environment: - KC_PROXY=edge - KC_HTTP_RELATIVE_PATH=/idp + - KC_HOSTNAME=localhost - KC_HOSTNAME_STRICT=false + - KC_HOSTNAME_STRICT_BACKCHANNEL=false + - KC_HTTPS_CERTIFICATE_FILE=/etc/ssl/certs/keycloak.tls.crt + - KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/ssl/certs/keycloak.tls.key - KC_HEALTH_ENABLED=true - KC_METRICS_ENABLED=true - KC_DB=postgres @@ -57,6 +61,7 @@ services: - ./keycloak-extra/health-check.sh:/opt/keycloak/health-check.sh - ./keycloak-realms/:/opt/keycloak/data/import - ./keycloak-themes/:/opt/keycloak/themes + - ./keycloak-certs/:/etc/ssl/certs/ networks: - default @@ -68,12 +73,12 @@ services: keycloak: condition: service_healthy command: + - --spring.profiles.active=insecure - --spring.webflux.base-path=/pid-issuer - --issuer.publicUrl=https://localhost/pid-issuer - --issuer.authorizationServer.publicUrl=https://localhost/idp/realms/pid-issuer-realm - - --issuer.authorizationServer=http://keycloak:8080/idp/realms/pid-issuer-realm - - --issuer.authorizationServer.introspection=http://keycloak:8080/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect - - --issuer.authorizationServer.userinfo=http://keycloak:8080/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo + - --issuer.authorizationServer.introspection=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect + - --issuer.authorizationServer.userinfo=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo - --issuer.pid.mso_mdoc.enabled=true - --issuer.pid.mso_mdoc.encoderUrl=https://preprod.issuer.eudiw.dev/formatter/cbor - --issuer.pid.sd_jwt_vc.enabled=true diff --git a/keycloak/keycloak-certs/keycloak.tls.crt b/keycloak/keycloak-certs/keycloak.tls.crt new file mode 100644 index 00000000..e2ca46d9 --- /dev/null +++ b/keycloak/keycloak-certs/keycloak.tls.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE2jCCAsKgAwIBAgIEZVdYUDANBgkqhkiG9w0BAQ0FADAgMQswCQYDVQQGEwJH +UjERMA8GA1UEAwwIa2V5Y2xvYWswIBcNMjMxMTE3MTIxMDU2WhgPMjEyMzExMTcx +MjEwNTZaMCAxCzAJBgNVBAYTAkdSMREwDwYDVQQDDAhrZXljbG9hazCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAOJj3CZsu1Lyx5Vgnb7xTSmT12nsPBWu +1aLELGMgKlaMlzteQHczvuEchyhswy2Slu+SK69v+fIMx/gETPlRA3dv25s6niRg +kARdOLKVWtNbfe73xlLzxs53o6tGYu3B3IqcEQ6rrY0290xv106yFFKjtuXrIjCK +xRq4feRc1iYxw50eQlGTH1JFDI/hTSrs0y5vChahYL3aJmKRoYdf/yFtc3md6bCG +AEnAa0d8bMiMgPFw97i4IMrRImpd2f9/Cf7f5UkmghUcQ2+EdpI6LfQwRqw6WcmM +PJiayEYBHfapvS7HJTw9WeLANoQ9SUe5kpA5oL0fAIzSmHNMl3XhNM59wmdAeweu +jjGSEYqs5+8mmvvxP0ORtQVeJjjSPJdBfWDVgd+8Zp9LfEOChA8eavQjMpv1Q/fW +LuwAOq4kdKGWv6aFcu+FvCADQ/kNhTU96StzjVyeNycGn4uxQBpWHSMR4zbb3d5r +B2vQ9EYrnTu6WqUT28hoAaCJ+dhBZ/n2Ph/FTMpNPKZPqN2MRQD3NkMJ0NVSqgzX +Kom5NRrCfjOW2wsNy2CTUkzy9kBzWTV6rWoZ1O79P48XRDApCCGxwg34ehjtdbv/ +2CG7A6jV45bJwaAdMQoliJiNOfBMQ22KMVY5/fkyzAj7zuJV1iLo/umq76+7ziQF +f2ZFGcU8N/pLAgMBAAGjGjAYMBYGA1UdEQEB/wQMMAqCCGtleWNsb2FrMA0GCSqG +SIb3DQEBDQUAA4ICAQA0ahabnZfv8+w/fi48HoypFzhDtHnKrd8P73n1nGgJ3/Ld +WG/9LE3xVXdAQzeqAXkpiGZ+KCzJVtT2QJFJmI48tmbxpSRH6LI4UY6OgsmhqADV +M3/HVx+Qkc2JQHrzPESJwOsXtfxmI/pwN3oVyO5Sp5IMvcJ0Cg7gWfeBtcYkJC91 ++AQBuwKvH7PxI0q/OvK52SR0vvMeZD14qhWARLHFkGQ/VMS2eSIMTS9mTHY9+pRx +4JwyUu/j5G8jH6KGM3laiJpoPGY1cQlvYL25bNIyLoThDbBKt5O6CUvEgrFHvtLk +u2WSCg+ErZnSYGndcydwEzqU6yFAVDEyqwSl3AJ+CVqxCOYEirhKHrl5MoeBitls +drab89+6zOgHFAIC5NLX+PPwVjq0e33wTCMKMMNLPjZk/S4TjjaR4IQmLx7zWxR2 +Ua/mlH2HwfjF4Y2v/GawTpfgN19oFds87BB2nAFmL2i3b/+x15RCZb8khrfT8Cpk +LyZusR3o9bQGEuK8yGxROWiXKWgXXmtlDA53/ZXu/m/u1234hJiF/C1jSjSeI4W1 +sahCkuXivAxc+h0yK+TftBWMZABTMywlOu89Tocv8Zgr3lYNQEf2MyZrIsyDs3DI +VhIvU8ig0f/Mt62JLoCTD/Dxai4S/iFJ4fOaAZhdCGABzWcOkfvR3BZoPt5yzw== +-----END CERTIFICATE----- diff --git a/keycloak/keycloak-certs/keycloak.tls.key b/keycloak/keycloak-certs/keycloak.tls.key new file mode 100644 index 00000000..a0aefc45 --- /dev/null +++ b/keycloak/keycloak-certs/keycloak.tls.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +R3yen8xSKBIYRzr8AoDtC2GtlXc9TOg= +-----END PRIVATE KEY----- From 94e132d45b23cb92b2f836d2dfdb1d4820ac0eef Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 14:37:58 +0200 Subject: [PATCH 07/15] Revert "Add the ability to expose the Authorization Server using a different URL in Credential Issuer Metadata." This reverts commit c5dacefd7816e7302f2102ec9d35ed7828536a48. --- keycloak/docker-compose.yaml | 2 +- .../eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/keycloak/docker-compose.yaml b/keycloak/docker-compose.yaml index 6e6c8d53..a419b01c 100644 --- a/keycloak/docker-compose.yaml +++ b/keycloak/docker-compose.yaml @@ -76,7 +76,7 @@ services: - --spring.profiles.active=insecure - --spring.webflux.base-path=/pid-issuer - --issuer.publicUrl=https://localhost/pid-issuer - - --issuer.authorizationServer.publicUrl=https://localhost/idp/realms/pid-issuer-realm + - --issuer.authorizationServer=https://localhost/idp/realms/pid-issuer-realm - --issuer.authorizationServer.introspection=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect - --issuer.authorizationServer.userinfo=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo - --issuer.pid.mso_mdoc.enabled=true diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index 563111a1..00d00c38 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -133,8 +133,7 @@ fun beans(clock: Clock) = beans { deferredCredentialEndpoint = env.readRequiredUrl("issuer.publicUrl").run { HttpsUrl.unsafe("${this.value}${WalletApi.DEFERRED_ENDPOINT}") }, - authorizationServer = env.readOptionalUrl("issuer.authorizationServer.publicUrl") - ?: env.readRequiredUrl("issuer.authorizationServer"), + authorizationServer = env.readRequiredUrl("issuer.authorizationServer"), credentialResponseEncryption = env.credentialResponseEncryption(), specificCredentialIssuers = buildList { @@ -312,9 +311,6 @@ private fun Environment.readRequiredUrl(key: String): HttpsUrl = HttpsUrl.of(url) ?: HttpsUrl.unsafe(url) } -private fun Environment.readOptionalUrl(key: String): HttpsUrl? = - getProperty(key)?.let { HttpsUrl.of(it) ?: HttpsUrl.unsafe(it) } - private fun Environment.readNonEmptySet(key: String, f: (String) -> T?): NonEmptySet { val nonEmptySet = getRequiredProperty>(key) .mapNotNull(f) From 0068151f9f5c3964060064e694ee2b524d44bebe Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 15:09:42 +0200 Subject: [PATCH 08/15] Update folder structure for docker-compose local dev environment. --- README.md | 4 ++-- {keycloak => docker-compose}/docker-compose.yaml | 8 ++++---- .../haproxy/certs/localhost.tls.crt | 0 .../haproxy/certs/localhost.tls.key | 0 .../haproxy/certs/localhost.tls.pem | 0 {keycloak => docker-compose}/haproxy/haproxy.conf | 0 .../keycloak/certs}/keycloak.tls.crt | 0 .../keycloak/certs}/keycloak.tls.key | 0 .../keycloak/extra}/health-check.sh | 0 .../keycloak/realms}/pid-issuer-realm-realm.json | 0 .../keycloak/realms}/pid-issuer-realm-users-0.json | 0 .../pid-issuer/login/messages/messages_en.properties | 0 .../keycloak/themes}/pid-issuer/login/register.ftl | 0 .../keycloak/themes}/pid-issuer/login/theme.properties | 0 14 files changed, 6 insertions(+), 6 deletions(-) rename {keycloak => docker-compose}/docker-compose.yaml (93%) rename {keycloak => docker-compose}/haproxy/certs/localhost.tls.crt (100%) rename {keycloak => docker-compose}/haproxy/certs/localhost.tls.key (100%) rename {keycloak => docker-compose}/haproxy/certs/localhost.tls.pem (100%) rename {keycloak => docker-compose}/haproxy/haproxy.conf (100%) rename {keycloak/keycloak-certs => docker-compose/keycloak/certs}/keycloak.tls.crt (100%) rename {keycloak/keycloak-certs => docker-compose/keycloak/certs}/keycloak.tls.key (100%) rename {keycloak/keycloak-extra => docker-compose/keycloak/extra}/health-check.sh (100%) rename {keycloak/keycloak-realms => docker-compose/keycloak/realms}/pid-issuer-realm-realm.json (100%) rename {keycloak/keycloak-realms => docker-compose/keycloak/realms}/pid-issuer-realm-users-0.json (100%) rename {keycloak/keycloak-themes => docker-compose/keycloak/themes}/pid-issuer/login/messages/messages_en.properties (100%) rename {keycloak/keycloak-themes => docker-compose/keycloak/themes}/pid-issuer/login/register.ftl (100%) rename {keycloak/keycloak-themes => docker-compose/keycloak/themes}/pid-issuer/login/theme.properties (100%) diff --git a/README.md b/README.md index 4f531eb3..781460c1 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ and requires the use of a suitable OAUTH2 server. ## How to use docker -Folder [keycloak](keycloak) contains a keycloak installation to be used in a local development environment +Folder [docker-compose](docker-compose) contains a keycloak installation to be used in a local development environment ```shell -cd keycloak +cd docker-compose docker-compose up -d ``` diff --git a/keycloak/docker-compose.yaml b/docker-compose/docker-compose.yaml similarity index 93% rename from keycloak/docker-compose.yaml rename to docker-compose/docker-compose.yaml index a419b01c..87fb1bd9 100644 --- a/keycloak/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -58,10 +58,10 @@ services: postgres: condition: service_healthy volumes: - - ./keycloak-extra/health-check.sh:/opt/keycloak/health-check.sh - - ./keycloak-realms/:/opt/keycloak/data/import - - ./keycloak-themes/:/opt/keycloak/themes - - ./keycloak-certs/:/etc/ssl/certs/ + - ./keycloak/extra/health-check.sh:/opt/keycloak/health-check.sh + - ./keycloak/realms/:/opt/keycloak/data/import + - ./keycloak/themes/:/opt/keycloak/themes + - ./keycloak/certs/:/etc/ssl/certs/ networks: - default diff --git a/keycloak/haproxy/certs/localhost.tls.crt b/docker-compose/haproxy/certs/localhost.tls.crt similarity index 100% rename from keycloak/haproxy/certs/localhost.tls.crt rename to docker-compose/haproxy/certs/localhost.tls.crt diff --git a/keycloak/haproxy/certs/localhost.tls.key b/docker-compose/haproxy/certs/localhost.tls.key similarity index 100% rename from keycloak/haproxy/certs/localhost.tls.key rename to docker-compose/haproxy/certs/localhost.tls.key diff --git a/keycloak/haproxy/certs/localhost.tls.pem b/docker-compose/haproxy/certs/localhost.tls.pem similarity index 100% rename from keycloak/haproxy/certs/localhost.tls.pem rename to docker-compose/haproxy/certs/localhost.tls.pem diff --git a/keycloak/haproxy/haproxy.conf b/docker-compose/haproxy/haproxy.conf similarity index 100% rename from keycloak/haproxy/haproxy.conf rename to docker-compose/haproxy/haproxy.conf diff --git a/keycloak/keycloak-certs/keycloak.tls.crt b/docker-compose/keycloak/certs/keycloak.tls.crt similarity index 100% rename from keycloak/keycloak-certs/keycloak.tls.crt rename to docker-compose/keycloak/certs/keycloak.tls.crt diff --git a/keycloak/keycloak-certs/keycloak.tls.key b/docker-compose/keycloak/certs/keycloak.tls.key similarity index 100% rename from keycloak/keycloak-certs/keycloak.tls.key rename to docker-compose/keycloak/certs/keycloak.tls.key diff --git a/keycloak/keycloak-extra/health-check.sh b/docker-compose/keycloak/extra/health-check.sh similarity index 100% rename from keycloak/keycloak-extra/health-check.sh rename to docker-compose/keycloak/extra/health-check.sh diff --git a/keycloak/keycloak-realms/pid-issuer-realm-realm.json b/docker-compose/keycloak/realms/pid-issuer-realm-realm.json similarity index 100% rename from keycloak/keycloak-realms/pid-issuer-realm-realm.json rename to docker-compose/keycloak/realms/pid-issuer-realm-realm.json diff --git a/keycloak/keycloak-realms/pid-issuer-realm-users-0.json b/docker-compose/keycloak/realms/pid-issuer-realm-users-0.json similarity index 100% rename from keycloak/keycloak-realms/pid-issuer-realm-users-0.json rename to docker-compose/keycloak/realms/pid-issuer-realm-users-0.json diff --git a/keycloak/keycloak-themes/pid-issuer/login/messages/messages_en.properties b/docker-compose/keycloak/themes/pid-issuer/login/messages/messages_en.properties similarity index 100% rename from keycloak/keycloak-themes/pid-issuer/login/messages/messages_en.properties rename to docker-compose/keycloak/themes/pid-issuer/login/messages/messages_en.properties diff --git a/keycloak/keycloak-themes/pid-issuer/login/register.ftl b/docker-compose/keycloak/themes/pid-issuer/login/register.ftl similarity index 100% rename from keycloak/keycloak-themes/pid-issuer/login/register.ftl rename to docker-compose/keycloak/themes/pid-issuer/login/register.ftl diff --git a/keycloak/keycloak-themes/pid-issuer/login/theme.properties b/docker-compose/keycloak/themes/pid-issuer/login/theme.properties similarity index 100% rename from keycloak/keycloak-themes/pid-issuer/login/theme.properties rename to docker-compose/keycloak/themes/pid-issuer/login/theme.properties From d38c5664b9a8cc44996507e117fe9f71fab1b66a Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 16:03:01 +0200 Subject: [PATCH 09/15] Update documentation for Keycloak in docker compose. --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 781460c1..66b64bff 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,35 @@ and requires the use of a suitable OAUTH2 server. ## How to use docker -Folder [docker-compose](docker-compose) contains a keycloak installation to be used in a local development environment +Folder [docker-compose](docker-compose) contains the following services to be used in a local development environment: +### Keycloak + +A Keycloak instance accessible via https://localhost/idp/ with the Realm *pid-issuer-realm*. + +The Realm *pid-issuer-realm*: + +- has user self-registration active with a custom registration page accessible via https://localhost/idp/realms/pid-issuer-realm/account/#/ +- defines *eu.europa.ec.eudiw.pid_vc_sd_jwt* scope for requesting PID issuance in SD JWT VC format +- defines *eu.europa.ec.eudiw.pid_mso_mdoc* scope for requesting PID issuance in MSO MDOC format +- defines *wallet-dev* and *pid-issuer-srv* clients +- contains sample user with credentials: tneal / password + +Administration console is accessible via https://localhost/idp/admin/ using the credentials admin / password + +### Startup ```shell cd docker-compose docker-compose up -d ``` +or + +```shell +cd docker-compose +docker compose up -d +``` ## Endpoints From 9a416f4b0216a42f6ff655a239f707f0bd0cda6b Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 16:28:35 +0200 Subject: [PATCH 10/15] Provide a WebClient bean based on the active Spring Profile. --- .../ec/eudi/pidissuer/PidIssuerApplication.kt | 51 +++++++++---------- .../pid/EncodePidInCborWithMicroService.kt | 31 +++-------- .../out/pid/GetPidDataFromAuthServer.kt | 44 ++-------------- .../adapter/out/webclient/WebClients.kt | 29 +++++++---- 4 files changed, 54 insertions(+), 101 deletions(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index 00d00c38..5ea7e458 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -63,6 +63,7 @@ import org.springframework.security.oauth2.server.resource.introspection.SpringR import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint import org.springframework.web.reactive.config.EnableWebFlux import org.springframework.web.reactive.config.WebFluxConfigurer +import org.springframework.web.reactive.function.client.WebClient import java.time.Clock import java.time.Duration @@ -72,22 +73,18 @@ fun beans(clock: Clock) = beans { // bean { clock } profile("!insecure") { - bean { - GetPidDataFromAuthServer( - env.readRequiredUrl("issuer.authorizationServer.userinfo"), - env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry), - clock, - ) - } + bean { WebClients.Default } } profile("insecure") { - bean { - GetPidDataFromAuthServer.insecure( - env.readRequiredUrl("issuer.authorizationServer.userinfo"), - env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry), - clock, - ) - } + bean { WebClients.Insecure } + } + bean { + GetPidDataFromAuthServer( + env.readRequiredUrl("issuer.authorizationServer.userinfo"), + env.getRequiredProperty("issuer.pid.issuingCountry").let(::IsoCountry), + clock, + ref(), + ) } // // Encryption of credential response @@ -122,7 +119,7 @@ fun beans(clock: Clock) = beans { val issuerPublicUrl = env.readRequiredUrl("issuer.publicUrl") bean { - EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl")) + EncodePidInCborWithMicroService(env.readRequiredUrl("issuer.pid.mso_mdoc.encoderUrl"), ref()) } CredentialIssuerMetaData( @@ -210,19 +207,17 @@ fun beans(clock: Clock) = beans { // // Security // - profile("insecure") { - bean { - val properties = ref() - - SpringReactiveOpaqueTokenIntrospector( - properties.opaquetoken.introspectionUri, - WebClients.insecure { - defaultHeaders { - it.setBasicAuth(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret) - } - }, - ) - } + bean { + val properties = ref() + SpringReactiveOpaqueTokenIntrospector( + properties.opaquetoken.introspectionUri, + ref() + .mutate() + .defaultHeaders { + it.setBasicAuth(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret) + } + .build(), + ) } bean { /* diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt index 9121d7e5..b8834a6f 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt @@ -25,35 +25,23 @@ import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemWriter import org.slf4j.LoggerFactory import org.springframework.http.MediaType -import org.springframework.http.codec.json.KotlinSerializationJsonDecoder -import org.springframework.http.codec.json.KotlinSerializationJsonEncoder import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.awaitBody -import org.springframework.web.reactive.function.client.awaitExchange import java.io.StringWriter import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi private val log = LoggerFactory.getLogger(EncodePidInCborWithMicroService::class.java) -class EncodePidInCborWithMicroService(private val creatorUrl: HttpsUrl) : EncodePidInCbor { +class EncodePidInCborWithMicroService( + private val creatorUrl: HttpsUrl, + private val webClient: WebClient, +) : EncodePidInCbor { init { log.info("Initialized using: $creatorUrl") } - private val webClient: WebClient by lazy { - val json = Json { ignoreUnknownKeys = true } - WebClient - .builder() - .codecs { configurer -> - configurer.defaultCodecs().kotlinSerializationJsonDecoder(KotlinSerializationJsonDecoder(json)) - configurer.defaultCodecs().kotlinSerializationJsonEncoder(KotlinSerializationJsonEncoder(json)) - configurer.defaultCodecs().enableLoggingRequestDetails(true) - }.baseUrl(creatorUrl.externalForm) - .build() - } - override suspend fun invoke( pid: Pid, pidMetaData: PidMetaData, @@ -63,17 +51,12 @@ class EncodePidInCborWithMicroService(private val creatorUrl: HttpsUrl) : Encode val request = createMsoMdocReq(pid, pidMetaData, holderKey) val response = webClient .post() + .uri(creatorUrl.externalForm) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) - .awaitExchange { response -> - val statusCode = response.statusCode() - when { - statusCode.is2xxSuccessful -> response.awaitBody() - statusCode.is4xxClientError -> response.awaitBody() - else -> error("Unexpected error statusCode = $statusCode") - } - } + .retrieve() + .awaitBody() response.fold().getOrThrow() } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt index caf5c845..5fc1bd64 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidDataFromAuthServer.kt @@ -17,7 +17,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.pid import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcAddressClaim import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.OidcAssurancePlaceOfBirth -import eu.europa.ec.eudi.pidissuer.adapter.out.webclient.WebClients import eu.europa.ec.eudi.pidissuer.domain.HttpsUrl import kotlinx.serialization.Required import kotlinx.serialization.SerialName @@ -33,7 +32,8 @@ import java.time.LocalDate private val log = LoggerFactory.getLogger(GetPidDataFromAuthServer::class.java) -class GetPidDataFromAuthServer private constructor( +class GetPidDataFromAuthServer( + private val authorizationServerUserInfoEndPoint: HttpsUrl, private val issuerCountry: IsoCountry, private val clock: Clock, private val webClient: WebClient, @@ -52,7 +52,9 @@ class GetPidDataFromAuthServer private constructor( } private suspend fun userInfo(accessToken: String): UserInfo = - webClient.get().accept(MediaType.APPLICATION_JSON) + webClient.get() + .uri(authorizationServerUserInfoEndPoint.externalForm) + .accept(MediaType.APPLICATION_JSON) .headers { headers -> headers.setBearerAuth(accessToken) } .retrieve() .awaitBody() @@ -93,42 +95,6 @@ class GetPidDataFromAuthServer private constructor( return pid to pidMetaData } - - companion object { - - /** - * Creates a new [GetPidDataFromAuthServer] using the provided data. - */ - operator fun invoke( - authorizationServerUserInfoEndPoint: HttpsUrl, - issuerCountry: IsoCountry, - clock: Clock, - ): GetPidDataFromAuthServer = - GetPidDataFromAuthServer( - issuerCountry, - clock, - WebClients.default { - baseUrl(authorizationServerUserInfoEndPoint.externalForm) - }, - ) - - /** - * Creates a new *insecure* [GetPidDataFromAuthServer] that trusts all certificates. - */ - fun insecure( - authorizationServerUserInfoEndPoint: HttpsUrl, - issuerCountry: IsoCountry, - clock: Clock, - ): GetPidDataFromAuthServer { - return GetPidDataFromAuthServer( - issuerCountry, - clock, - WebClients.insecure { - baseUrl(authorizationServerUserInfoEndPoint.externalForm) - }, - ) - } - } } @Serializable diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/webclient/WebClients.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/webclient/WebClients.kt index 4e4154a1..9a5f99df 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/webclient/WebClients.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/webclient/WebClients.kt @@ -17,38 +17,47 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.webclient import io.netty.handler.ssl.SslContextBuilder import io.netty.handler.ssl.util.InsecureTrustManagerFactory +import kotlinx.serialization.json.Json import org.slf4j.LoggerFactory import org.springframework.http.client.reactive.ReactorClientHttpConnector +import org.springframework.http.codec.json.KotlinSerializationJsonDecoder +import org.springframework.http.codec.json.KotlinSerializationJsonEncoder import org.springframework.web.reactive.function.client.WebClient import reactor.netty.http.client.HttpClient -private val log = LoggerFactory.getLogger(WebClients::class.java) - /** - * Factories for [WebClient]. + * [WebClient] instances for usage in the application. */ internal object WebClients { + private val log = LoggerFactory.getLogger(WebClients::class.java) + /** - * Creates a new [WebClient]. + * A [WebClient] with JSON serialization/deserialization enabled using Kotlin-X Serialization. */ - fun default(customizer: WebClient.Builder.() -> Unit = {}): WebClient = + val Default: WebClient by lazy { + val json = Json { ignoreUnknownKeys = true } WebClient.builder() - .apply(customizer) + .codecs { + it.defaultCodecs().kotlinSerializationJsonDecoder(KotlinSerializationJsonDecoder(json)) + it.defaultCodecs().kotlinSerializationJsonEncoder(KotlinSerializationJsonEncoder(json)) + it.defaultCodecs().enableLoggingRequestDetails(true) + } .build() + } /** - * Creates an *insecure* [WebClient] that trusts all certificates. + * A [WebClient] with JSON serialization/deserialization enabled using Kotlin-X Serialization + * that trusts all SSL certificates. */ - fun insecure(customizer: WebClient.Builder.() -> Unit = {}): WebClient { + val Insecure: WebClient by lazy { log.warn("Using insecure WebClient trusting all certificates") val sslContext = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build() val httpClient = HttpClient.create().secure { it.sslContext(sslContext) } - return WebClient.builder() + Default.mutate() .clientConnector(ReactorClientHttpConnector(httpClient)) - .apply(customizer) .build() } } From 7a31fbe8da879c8fb44c42ec333af2850270ee36 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 16:29:00 +0200 Subject: [PATCH 11/15] Always set 'FC' as the country in EncodePidInCborWithMicroService. --- .../adapter/out/pid/EncodePidInCborWithMicroService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt index b8834a6f..07583d27 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInCborWithMicroService.kt @@ -79,7 +79,7 @@ internal fun createMsoMdocReq( ): JsonObject = buildJsonObject { put("version", "0.3") - put("country", pidMetaData.issuingCountry.value) + put("country", "FC") put("doctype", PidMsoMdocV1.docType) put("device_publickey", key.base64EncodedPem()) putJsonObject("data") { From 7bd553257f5a6b1e9a5db8bc7c6f24accdf8459a Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 17:00:42 +0200 Subject: [PATCH 12/15] Prefer using environment variables to configured pid-issuer in docker-compose.yaml --- docker-compose/docker-compose.yaml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 87fb1bd9..dfbb0930 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -72,20 +72,19 @@ services: depends_on: keycloak: condition: service_healthy - command: - - --spring.profiles.active=insecure - - --spring.webflux.base-path=/pid-issuer - - --issuer.publicUrl=https://localhost/pid-issuer - - --issuer.authorizationServer=https://localhost/idp/realms/pid-issuer-realm - - --issuer.authorizationServer.introspection=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect - - --issuer.authorizationServer.userinfo=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo - - --issuer.pid.mso_mdoc.enabled=true - - --issuer.pid.mso_mdoc.encoderUrl=https://preprod.issuer.eudiw.dev/formatter/cbor - - --issuer.pid.sd_jwt_vc.enabled=true - - --issuer.pid.sd_jwt_vc.deferred=true - - --issuer.pid.issuingCountry=FC - - --spring.security.oauth2.resourceserver.opaquetoken.client-id=pid-issuer-srv - - --spring.security.oauth2.resourceserver.opaquetoken.client-secret=zIKAV9DIIIaJCzHCVBPlySgU8KgY68U2 + environment: + - SPRING_PROFILES_ACTIVE=insecure + - SPRING_WEBFLUX_BASE_PATH=/pid-issuer + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_OPAQUETOKEN_CLIENT_ID=pid-issuer-srv + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_OPAQUETOKEN_CLIENT_SECRET=zIKAV9DIIIaJCzHCVBPlySgU8KgY68U2 + - ISSUER_PUBLIC_URL=https://localhost/pid-issuer + - ISSUER_AUTHORIZATIONSERVER=https://localhost/idp/realms/pid-issuer-realm + - ISSUER_AUTHORIZATIONSERVER_INTROSPECTION=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/token/introspect + - ISSUER_AUTHORIZATIONSERVER_USERINFO=https://keycloak:8443/idp/realms/pid-issuer-realm/protocol/openid-connect/userinfo + - ISSUER_PID_MSO_MDOC_ENABLED=true + - ISSUER_PID_SD_JWT_VC_ENABLED=true + - ISSUER_PID_SD_JWT_VC_DEFERRED=true + - ISSUER_PID_ISSUING_COUNTRY=GR networks: - default From 61bea5bbab6b15ea522f8ea2d4afde24b987c300 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 17:11:25 +0200 Subject: [PATCH 13/15] Further document docker-compose. --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66b64bff..8b4be421 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,21 @@ The Realm *pid-issuer-realm*: Administration console is accessible via https://localhost/idp/admin/ using the credentials admin / password -### Startup +### PID Issuer + +A PID Issuer instance accessible via https://localhost/pid-issuer/ + +It uses the configured Keycloak instance as an Authorization Server, and PID issuance both *SD JWT VC* and *MSO MDOC* +formats is enabled. Additionally *deferred issuance* is enabled for *SD JWT VC* format. + +The issuing country is set to GR (Greece). + +### HA Proxy + +An HA Proxy instance is also configured. This instance exposes both Keyclaok and PID Issuer via https. The certificate +and respective private key can be found in [docker-compose/haproxy/certs](docker-compose/haproxy/certs). + +### docker compose usage ```shell cd docker-compose From 894c107e6df81efe6ca97f53433cc154eb05c047 Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 17:18:38 +0200 Subject: [PATCH 14/15] Consider WebFlux base path in default public URL. --- src/main/resources/application-prod.properties | 4 +++- src/main/resources/application.properties | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 6b164c0f..d9ddcea9 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -6,10 +6,12 @@ management.endpoints.enabled-by-default=true server.error.includeStacktrace=ALWAYS server.port=8080 +spring.webflux.base-path=/ + # # Issuer options # -issuer.publicUrl=http://localhost:${server.port} +issuer.publicUrl=http://localhost:${server.port}/${spring.webflux.base-path} issuer.authorizationServer=https://keycloak-eudi.netcompany-intrasoft.com/realms/pid-issuer-realm issuer.authorizationServer.introspection=${issuer.authorizationServer}/protocol/openid-connect/token/introspect issuer.authorizationServer.userinfo=${issuer.authorizationServer}/protocol/openid-connect/userinfo diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 501b962e..6177cda2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,10 +6,12 @@ management.endpoints.enabled-by-default=true server.error.includeStacktrace=ALWAYS server.port=8080 +spring.webflux.base-path=/ + # # Issuer options # -issuer.publicUrl=http://localhost:${server.port} +issuer.publicUrl=http://localhost:${server.port}/${spring.webflux.base-path} issuer.authorizationServer=https://localhost/idp/realms/pid-issuer-realm issuer.authorizationServer.introspection=${issuer.authorizationServer}/protocol/openid-connect/token/introspect issuer.authorizationServer.userinfo=${issuer.authorizationServer}/protocol/openid-connect/userinfo From 1a921336935feea2377a77f51c0cfe349e2c05df Mon Sep 17 00:00:00 2001 From: Dimitris ZARRAS Date: Fri, 17 Nov 2023 17:26:36 +0200 Subject: [PATCH 15/15] Document the environment variables that can be used to configure the PID issuer application. --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8b4be421..63249601 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,26 @@ cd docker-compose docker compose up -d ``` +## Configuration + +The PID Issuer application can be configured using the following *environment variables*: + +| Environment variable | Description | Default value | +|-----------------------------------------------------------------|---------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| SPRING_PROFILES_ACTIVE | Spring profiles to enable. | None. Enable *insecure* profile to disable SSL certificates verification | +| SPRING_WEBFLUX_BASE_PATH | Context path for the PID issuer application. | / | +| SERVER_PORT | Port for the HTTP listener of the PID Isser application | 8080 | +| SPRING_SECURITY_OAUTH2_RESOURCESERVER_OPAQUETOKEN_CLIENT_ID | Client Id of the OAuth2 client registered in the Authorization Server | N/A | +| SPRING_SECURITY_OAUTH2_RESOURCESERVER_OPAQUETOKEN_CLIENT_SECRET | Client Server of the OAuth2 client registered in the Authorization Server | N/A | +| ISSUER_PUBLIC_URL | URL the PID Issuer application is accessible from. | http://localhost:${SERVER_PORT}/${SPRING_WEBFLUX_BASE_PATH} | +| ISSUER_AUTHORIZATIONSERVER | URL of the Authorization Server | N/A | +| ISSUER_AUTHORIZATIONSERVER_INTROSPECTION | URL of the Token Introspection endpoint of the Authorization Server | N/A | +| ISSUER_AUTHORIZATIONSERVER_USERINFO | URL of the UserInfo endpoint of the Authorization Server | N/A | +| ISSUER_PID_MSO_MDOC_ENABLED | Whether to enable support for PID issuance in *MSO MDOC* format | true | +| ISSUER_PID_SD_JWT_VC_ENABLED | Whether to enable support for PID issuance in *SD JWT VC* format | true | +| ISSUER_PID_SD_JWT_VC_DEFERRED | Whether PID issueance in *SD JWT VC* format should be *deferred* or *immediate* | false (i.e. immediate issuance) | +| ISSUER_PID_ISSUING_COUNTRY | Code of the Country issuing the PID | N/A | + ## Endpoints ### Credential Issuer MetaData