From d82f636aaecb42ede14436bd5c55b2b53988d023 Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Tue, 1 Apr 2025 15:20:46 -0500 Subject: [PATCH 1/6] Add support for deployment to aca and ssl. --- .../KeycloakResourceBuilderExtensions.cs | 14 +++++- .../KeycloakResourceBuilderTests.cs | 50 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 773b514e068..f7788780576 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -3,6 +3,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Keycloak; +using System.Globalization; namespace Aspire.Hosting; @@ -14,8 +15,17 @@ public static class KeycloakResourceBuilderExtensions private const string AdminEnvVarName = "KC_BOOTSTRAP_ADMIN_USERNAME"; private const string AdminPasswordEnvVarName = "KC_BOOTSTRAP_ADMIN_PASSWORD"; private const string HealthCheckEnvVarName = "KC_HEALTH_ENABLED"; // As per https://www.keycloak.org/observability/health + private const string ProxyEdgeEnvVarName = "KC_PROXY"; + private const string HttpPortEnvVarName = "KC_HTTP_PORT"; + private const string HttpEnabledEnvVarName = "KC_HTTP_ENABLED"; + private const string HostNamePortEnvVarName = "KC_HOSTNAME_PORT"; + private const string HostNameStrictBackchannelEnvVarName = "KC_HOSTNAME_STRICT_BACKCHANNEL"; + private const string ProxyHeadersEnvVarName = "KC_PROXY_HEADERS"; + private const string HostNameStrictEnvVarName = "KC_HOSTNAME_STRICT"; + private const string HostNameStrictHttpsEnvVarName = "KC_HOSTNAME_STRICT_HTTPS"; private const int DefaultContainerPort = 8080; + private const int HttpsContainerPort = 8443; private const int ManagementInterfaceContainerPort = 9000; // As per https://www.keycloak.org/server/management-interface private const string ManagementEndpointName = "management"; private const string RealmImportDirectory = "/opt/keycloak/data/import"; @@ -56,12 +66,14 @@ public static IResourceBuilder AddKeycloak( var resource = new KeycloakResource(name, adminUsername?.Resource, passwordParameter); + var targetPort = port == HttpsContainerPort ? HttpsContainerPort : DefaultContainerPort; + var keycloak = builder .AddResource(resource) .WithImage(KeycloakContainerImageTags.Image) .WithImageRegistry(KeycloakContainerImageTags.Registry) .WithImageTag(KeycloakContainerImageTags.Tag) - .WithHttpEndpoint(port: port, targetPort: DefaultContainerPort) + .WithHttpEndpoint(port: port, targetPort: targetPort) .WithHttpEndpoint(targetPort: ManagementInterfaceContainerPort, name: ManagementEndpointName) .WithHttpHealthCheck(endpointName: ManagementEndpointName, path: "/health/ready") .WithEnvironment(context => diff --git a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs index 4fa9d482af1..143989bb9ea 100644 --- a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs +++ b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs @@ -107,7 +107,7 @@ public void AddAddKeycloakDoesNotAddGeneratedPasswordParameterWithUserSecretsPar } [Fact] - public async Task VerifyManifest() + public async Task VerifyManifestForHttp() { using var builder = TestDistributedApplicationBuilder.Create(); var keycloak = builder.AddKeycloak("keycloak"); @@ -145,4 +145,52 @@ public async Task VerifyManifest() """; Assert.Equal(expectedManifest, manifest.ToString()); } + + [Fact] + public async Task VerifyManifestForHttps() + { + using var builder = TestDistributedApplicationBuilder.Create(); + var keycloak = builder.AddKeycloak("keycloak", 8443); + + var manifest = await ManifestUtils.GetManifest(keycloak.Resource); + + var expectedManifest = $$""" + { + "type": "container.v0", + "image": "{{KeycloakContainerImageTags.Registry}}/{{KeycloakContainerImageTags.Image}}:{{KeycloakContainerImageTags.Tag}}", + "args": [ + "start-dev", + "--import-realm" + ], + "env": { + "KC_BOOTSTRAP_ADMIN_USERNAME": "admin", + "KC_BOOTSTRAP_ADMIN_PASSWORD": "{keycloak-password.value}", + "KC_HEALTH_ENABLED": "true", + "KC_PROXY": "edge", + "KC_HTTP_PORT": "8443", + "KC_HTTP_ENABLED": "true", + "KC_HOSTNAME_PORT": "8443", + "KC_HOSTNAME_STRICT_BACKCHANNEL": "false", + "KC_PROXY_HEADERS": "xforwarded", + "KC_HOSTNAME_STRICT": "false", + "KC_HOSTNAME_STRICT_HTTPS": "false" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "targetPort": 8443 + }, + "management": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "targetPort": 9000 + } + } + } + """; + Assert.Equal(expectedManifest, manifest.ToString()); + } } From ff1ba2286bf1703b110f89d859a411ef598eb46b Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Wed, 23 Apr 2025 11:34:07 -0500 Subject: [PATCH 2/6] Strip to bare minimum reverse proxy config. --- .../KeycloakResourceBuilderExtensions.cs | 13 ++--- .../KeycloakResourceBuilderTests.cs | 51 ++----------------- 2 files changed, 7 insertions(+), 57 deletions(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index f7788780576..6e14dc712f9 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -15,17 +15,11 @@ public static class KeycloakResourceBuilderExtensions private const string AdminEnvVarName = "KC_BOOTSTRAP_ADMIN_USERNAME"; private const string AdminPasswordEnvVarName = "KC_BOOTSTRAP_ADMIN_PASSWORD"; private const string HealthCheckEnvVarName = "KC_HEALTH_ENABLED"; // As per https://www.keycloak.org/observability/health - private const string ProxyEdgeEnvVarName = "KC_PROXY"; - private const string HttpPortEnvVarName = "KC_HTTP_PORT"; private const string HttpEnabledEnvVarName = "KC_HTTP_ENABLED"; - private const string HostNamePortEnvVarName = "KC_HOSTNAME_PORT"; - private const string HostNameStrictBackchannelEnvVarName = "KC_HOSTNAME_STRICT_BACKCHANNEL"; private const string ProxyHeadersEnvVarName = "KC_PROXY_HEADERS"; private const string HostNameStrictEnvVarName = "KC_HOSTNAME_STRICT"; - private const string HostNameStrictHttpsEnvVarName = "KC_HOSTNAME_STRICT_HTTPS"; private const int DefaultContainerPort = 8080; - private const int HttpsContainerPort = 8443; private const int ManagementInterfaceContainerPort = 9000; // As per https://www.keycloak.org/server/management-interface private const string ManagementEndpointName = "management"; private const string RealmImportDirectory = "/opt/keycloak/data/import"; @@ -66,14 +60,12 @@ public static IResourceBuilder AddKeycloak( var resource = new KeycloakResource(name, adminUsername?.Resource, passwordParameter); - var targetPort = port == HttpsContainerPort ? HttpsContainerPort : DefaultContainerPort; - var keycloak = builder .AddResource(resource) .WithImage(KeycloakContainerImageTags.Image) .WithImageRegistry(KeycloakContainerImageTags.Registry) .WithImageTag(KeycloakContainerImageTags.Tag) - .WithHttpEndpoint(port: port, targetPort: targetPort) + .WithHttpEndpoint(port: port, targetPort: DefaultContainerPort) .WithHttpEndpoint(targetPort: ManagementInterfaceContainerPort, name: ManagementEndpointName) .WithHttpHealthCheck(endpointName: ManagementEndpointName, path: "/health/ready") .WithEnvironment(context => @@ -81,6 +73,9 @@ public static IResourceBuilder AddKeycloak( context.EnvironmentVariables[AdminEnvVarName] = resource.AdminReference; context.EnvironmentVariables[AdminPasswordEnvVarName] = resource.AdminPasswordParameter; context.EnvironmentVariables[HealthCheckEnvVarName] = "true"; + context.EnvironmentVariables[HttpEnabledEnvVarName] = "true"; + context.EnvironmentVariables[ProxyHeadersEnvVarName] = "xforwarded"; + context.EnvironmentVariables[HostNameStrictEnvVarName] = "false"; }) .WithUrlForEndpoint(ManagementEndpointName, u => u.DisplayLocation = UrlDisplayLocation.DetailsOnly); diff --git a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs index 143989bb9ea..297ea013e5d 100644 --- a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs +++ b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs @@ -107,53 +107,13 @@ public void AddAddKeycloakDoesNotAddGeneratedPasswordParameterWithUserSecretsPar } [Fact] - public async Task VerifyManifestForHttp() + public async Task VerifyManifest() { using var builder = TestDistributedApplicationBuilder.Create(); var keycloak = builder.AddKeycloak("keycloak"); var manifest = await ManifestUtils.GetManifest(keycloak.Resource); - var expectedManifest = $$""" - { - "type": "container.v0", - "image": "{{KeycloakContainerImageTags.Registry}}/{{KeycloakContainerImageTags.Image}}:{{KeycloakContainerImageTags.Tag}}", - "args": [ - "start-dev", - "--import-realm" - ], - "env": { - "KC_BOOTSTRAP_ADMIN_USERNAME": "admin", - "KC_BOOTSTRAP_ADMIN_PASSWORD": "{keycloak-password.value}", - "KC_HEALTH_ENABLED": "true" - }, - "bindings": { - "http": { - "scheme": "http", - "protocol": "tcp", - "transport": "http", - "targetPort": 8080 - }, - "management": { - "scheme": "http", - "protocol": "tcp", - "transport": "http", - "targetPort": 9000 - } - } - } - """; - Assert.Equal(expectedManifest, manifest.ToString()); - } - - [Fact] - public async Task VerifyManifestForHttps() - { - using var builder = TestDistributedApplicationBuilder.Create(); - var keycloak = builder.AddKeycloak("keycloak", 8443); - - var manifest = await ManifestUtils.GetManifest(keycloak.Resource); - var expectedManifest = $$""" { "type": "container.v0", @@ -166,21 +126,16 @@ public async Task VerifyManifestForHttps() "KC_BOOTSTRAP_ADMIN_USERNAME": "admin", "KC_BOOTSTRAP_ADMIN_PASSWORD": "{keycloak-password.value}", "KC_HEALTH_ENABLED": "true", - "KC_PROXY": "edge", - "KC_HTTP_PORT": "8443", "KC_HTTP_ENABLED": "true", - "KC_HOSTNAME_PORT": "8443", - "KC_HOSTNAME_STRICT_BACKCHANNEL": "false", "KC_PROXY_HEADERS": "xforwarded", - "KC_HOSTNAME_STRICT": "false", - "KC_HOSTNAME_STRICT_HTTPS": "false" + "KC_HOSTNAME_STRICT": "false" }, "bindings": { "http": { "scheme": "http", "protocol": "tcp", "transport": "http", - "targetPort": 8443 + "targetPort": 8080 }, "management": { "scheme": "http", From 374cfb516e4db1b22b6414ada746f122d826c7ad Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Wed, 23 Apr 2025 11:42:27 -0500 Subject: [PATCH 3/6] Remove unused using. --- src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 6e14dc712f9..185be32d169 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -3,7 +3,6 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Keycloak; -using System.Globalization; namespace Aspire.Hosting; From ccd2910525164379baed3d971c82f893ed357f6f Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Tue, 1 Apr 2025 15:20:46 -0500 Subject: [PATCH 4/6] Add support for deployment to aca and ssl. --- .../KeycloakResourceBuilderExtensions.cs | 6 ++- .../KeycloakResourceBuilderTests.cs | 50 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 185be32d169..78f3305d1de 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -3,6 +3,7 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Keycloak; +using System.Globalization; namespace Aspire.Hosting; @@ -19,6 +20,7 @@ public static class KeycloakResourceBuilderExtensions private const string HostNameStrictEnvVarName = "KC_HOSTNAME_STRICT"; private const int DefaultContainerPort = 8080; + private const int HttpsContainerPort = 8443; private const int ManagementInterfaceContainerPort = 9000; // As per https://www.keycloak.org/server/management-interface private const string ManagementEndpointName = "management"; private const string RealmImportDirectory = "/opt/keycloak/data/import"; @@ -59,12 +61,14 @@ public static IResourceBuilder AddKeycloak( var resource = new KeycloakResource(name, adminUsername?.Resource, passwordParameter); + var targetPort = port == HttpsContainerPort ? HttpsContainerPort : DefaultContainerPort; + var keycloak = builder .AddResource(resource) .WithImage(KeycloakContainerImageTags.Image) .WithImageRegistry(KeycloakContainerImageTags.Registry) .WithImageTag(KeycloakContainerImageTags.Tag) - .WithHttpEndpoint(port: port, targetPort: DefaultContainerPort) + .WithHttpEndpoint(port: port, targetPort: targetPort) .WithHttpEndpoint(targetPort: ManagementInterfaceContainerPort, name: ManagementEndpointName) .WithHttpHealthCheck(endpointName: ManagementEndpointName, path: "/health/ready") .WithEnvironment(context => diff --git a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs index 297ea013e5d..d603b4fbcc5 100644 --- a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs +++ b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs @@ -107,7 +107,7 @@ public void AddAddKeycloakDoesNotAddGeneratedPasswordParameterWithUserSecretsPar } [Fact] - public async Task VerifyManifest() + public async Task VerifyManifestForHttp() { using var builder = TestDistributedApplicationBuilder.Create(); var keycloak = builder.AddKeycloak("keycloak"); @@ -148,4 +148,52 @@ public async Task VerifyManifest() """; Assert.Equal(expectedManifest, manifest.ToString()); } + + [Fact] + public async Task VerifyManifestForHttps() + { + using var builder = TestDistributedApplicationBuilder.Create(); + var keycloak = builder.AddKeycloak("keycloak", 8443); + + var manifest = await ManifestUtils.GetManifest(keycloak.Resource); + + var expectedManifest = $$""" + { + "type": "container.v0", + "image": "{{KeycloakContainerImageTags.Registry}}/{{KeycloakContainerImageTags.Image}}:{{KeycloakContainerImageTags.Tag}}", + "args": [ + "start-dev", + "--import-realm" + ], + "env": { + "KC_BOOTSTRAP_ADMIN_USERNAME": "admin", + "KC_BOOTSTRAP_ADMIN_PASSWORD": "{keycloak-password.value}", + "KC_HEALTH_ENABLED": "true", + "KC_PROXY": "edge", + "KC_HTTP_PORT": "8443", + "KC_HTTP_ENABLED": "true", + "KC_HOSTNAME_PORT": "8443", + "KC_HOSTNAME_STRICT_BACKCHANNEL": "false", + "KC_PROXY_HEADERS": "xforwarded", + "KC_HOSTNAME_STRICT": "false", + "KC_HOSTNAME_STRICT_HTTPS": "false" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "targetPort": 8443 + }, + "management": { + "scheme": "http", + "protocol": "tcp", + "transport": "http", + "targetPort": 9000 + } + } + } + """; + Assert.Equal(expectedManifest, manifest.ToString()); + } } From a6da5c8ba57b15a5cb37b8710fb3f62eefe9f0f1 Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Wed, 23 Apr 2025 11:34:07 -0500 Subject: [PATCH 5/6] Strip to bare minimum reverse proxy config. --- .../KeycloakResourceBuilderExtensions.cs | 5 +- .../KeycloakResourceBuilderTests.cs | 50 +------------------ 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 78f3305d1de..6e14dc712f9 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -20,7 +20,6 @@ public static class KeycloakResourceBuilderExtensions private const string HostNameStrictEnvVarName = "KC_HOSTNAME_STRICT"; private const int DefaultContainerPort = 8080; - private const int HttpsContainerPort = 8443; private const int ManagementInterfaceContainerPort = 9000; // As per https://www.keycloak.org/server/management-interface private const string ManagementEndpointName = "management"; private const string RealmImportDirectory = "/opt/keycloak/data/import"; @@ -61,14 +60,12 @@ public static IResourceBuilder AddKeycloak( var resource = new KeycloakResource(name, adminUsername?.Resource, passwordParameter); - var targetPort = port == HttpsContainerPort ? HttpsContainerPort : DefaultContainerPort; - var keycloak = builder .AddResource(resource) .WithImage(KeycloakContainerImageTags.Image) .WithImageRegistry(KeycloakContainerImageTags.Registry) .WithImageTag(KeycloakContainerImageTags.Tag) - .WithHttpEndpoint(port: port, targetPort: targetPort) + .WithHttpEndpoint(port: port, targetPort: DefaultContainerPort) .WithHttpEndpoint(targetPort: ManagementInterfaceContainerPort, name: ManagementEndpointName) .WithHttpHealthCheck(endpointName: ManagementEndpointName, path: "/health/ready") .WithEnvironment(context => diff --git a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs index d603b4fbcc5..297ea013e5d 100644 --- a/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs +++ b/tests/Aspire.Hosting.Keycloak.Tests/KeycloakResourceBuilderTests.cs @@ -107,7 +107,7 @@ public void AddAddKeycloakDoesNotAddGeneratedPasswordParameterWithUserSecretsPar } [Fact] - public async Task VerifyManifestForHttp() + public async Task VerifyManifest() { using var builder = TestDistributedApplicationBuilder.Create(); var keycloak = builder.AddKeycloak("keycloak"); @@ -148,52 +148,4 @@ public async Task VerifyManifestForHttp() """; Assert.Equal(expectedManifest, manifest.ToString()); } - - [Fact] - public async Task VerifyManifestForHttps() - { - using var builder = TestDistributedApplicationBuilder.Create(); - var keycloak = builder.AddKeycloak("keycloak", 8443); - - var manifest = await ManifestUtils.GetManifest(keycloak.Resource); - - var expectedManifest = $$""" - { - "type": "container.v0", - "image": "{{KeycloakContainerImageTags.Registry}}/{{KeycloakContainerImageTags.Image}}:{{KeycloakContainerImageTags.Tag}}", - "args": [ - "start-dev", - "--import-realm" - ], - "env": { - "KC_BOOTSTRAP_ADMIN_USERNAME": "admin", - "KC_BOOTSTRAP_ADMIN_PASSWORD": "{keycloak-password.value}", - "KC_HEALTH_ENABLED": "true", - "KC_PROXY": "edge", - "KC_HTTP_PORT": "8443", - "KC_HTTP_ENABLED": "true", - "KC_HOSTNAME_PORT": "8443", - "KC_HOSTNAME_STRICT_BACKCHANNEL": "false", - "KC_PROXY_HEADERS": "xforwarded", - "KC_HOSTNAME_STRICT": "false", - "KC_HOSTNAME_STRICT_HTTPS": "false" - }, - "bindings": { - "http": { - "scheme": "http", - "protocol": "tcp", - "transport": "http", - "targetPort": 8443 - }, - "management": { - "scheme": "http", - "protocol": "tcp", - "transport": "http", - "targetPort": 9000 - } - } - } - """; - Assert.Equal(expectedManifest, manifest.ToString()); - } } From 3f24659772a29b8387435983185dc2778d8b3879 Mon Sep 17 00:00:00 2001 From: Adam Anderly Date: Wed, 23 Apr 2025 11:42:27 -0500 Subject: [PATCH 6/6] Remove unused using. --- src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 6e14dc712f9..185be32d169 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -3,7 +3,6 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Keycloak; -using System.Globalization; namespace Aspire.Hosting;