From c9d6bc78c66d791c4ee09a50e586b66c5b3f549c Mon Sep 17 00:00:00 2001 From: Maria Stypsanelli Date: Thu, 27 Jun 2024 11:47:19 +0300 Subject: [PATCH 1/5] Move the health endpoints under the /internal-api route --- .../shared-kernel/ApiCore/Endpoints/HealthEndpoints.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/shared-kernel/ApiCore/Endpoints/HealthEndpoints.cs b/application/shared-kernel/ApiCore/Endpoints/HealthEndpoints.cs index b3e99c007..ad5d1cf17 100644 --- a/application/shared-kernel/ApiCore/Endpoints/HealthEndpoints.cs +++ b/application/shared-kernel/ApiCore/Endpoints/HealthEndpoints.cs @@ -9,9 +9,9 @@ public class HealthEndpoints : IEndpoints public void MapEndpoints(IEndpointRouteBuilder routes) { // All health checks must pass for app to be considered ready to accept traffic after starting - routes.MapHealthChecks("/health"); + routes.MapHealthChecks("/internal-api/ready"); // Only health checks tagged with the "live" tag must pass for app to be considered alive - routes.MapHealthChecks("/alive", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") }); + routes.MapHealthChecks("/internal-api/live", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") }); } } From 647daf8a11a4397edc629130a0459a39c96c4e84 Mon Sep 17 00:00:00 2001 From: Maria Stypsanelli Date: Wed, 8 May 2024 21:28:32 +0300 Subject: [PATCH 2/5] Configure Azure Container Apps to use internal-api/live and ready endpoints for health monitoring --- .../cluster/main-cluster.bicep | 5 ++ .../modules/container-app.bicep | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/cloud-infrastructure/cluster/main-cluster.bicep b/cloud-infrastructure/cluster/main-cluster.bicep index 2bd935133..32f503088 100644 --- a/cloud-infrastructure/cluster/main-cluster.bicep +++ b/cloud-infrastructure/cluster/main-cluster.bicep @@ -233,6 +233,7 @@ module accountManagementWorkers '../modules/container-app.bicep' = { maxReplicas: 3 userAssignedIdentityName: accountManagementIdentityName ingress: true + hasProbesEndpoint: true environmentVariables: accountManagementEnvironmentVariables } dependsOn: [accountManagementDatabase, accountManagementIdentity, communicationService] @@ -257,6 +258,7 @@ module accountManagementApi '../modules/container-app.bicep' = { maxReplicas: 3 userAssignedIdentityName: accountManagementIdentityName ingress: true + hasProbesEndpoint: true environmentVariables: accountManagementEnvironmentVariables } dependsOn: [accountManagementDatabase, accountManagementIdentity, communicationService, accountManagementWorkers] @@ -358,6 +360,7 @@ module backOfficeWorkers '../modules/container-app.bicep' = { maxReplicas: 1 userAssignedIdentityName: backOfficeIdentityName ingress: true + hasProbesEndpoint: true environmentVariables: backOfficeEnvironmentVariables } dependsOn: [backOfficeDatabase, backOfficeIdentity, communicationService] @@ -382,6 +385,7 @@ module backOfficeApi '../modules/container-app.bicep' = { maxReplicas: 1 userAssignedIdentityName: backOfficeIdentityName ingress: true + hasProbesEndpoint: true environmentVariables: backOfficeEnvironmentVariables } dependsOn: [backOfficeDatabase, backOfficeIdentity, communicationService, backOfficeWorkers] @@ -423,6 +427,7 @@ module appGateway '../modules/container-app.bicep' = { maxReplicas: 3 userAssignedIdentityName: appGatewayIdentityName ingress: true + hasProbesEndpoint: false domainName: domainName == '' ? '' : domainName isDomainConfigured: domainName != '' && isDomainConfigured external: true diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index 1bf36b4d0..e9646948d 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -13,6 +13,7 @@ param minReplicas int = 1 param maxReplicas int = 3 param userAssignedIdentityName string param ingress bool +param hasProbesEndpoint bool param domainName string = '' param isDomainConfigured bool = false param external bool = false @@ -93,6 +94,58 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-02-preview' = { memory: memory } env: environmentVariables + probes: hasProbesEndpoint + ? [ + { + type: 'Liveness' + httpGet: { + path: '/internal-api/live' + port: 8080 + scheme: 'HTTPS' + } + initialDelaySeconds: 3 + failureThreshold: 3 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + } + { + type: 'Readiness' + httpGet: { + path: '/internal-api/ready' + port: 8080 + scheme: 'HTTPS' + } + initialDelaySeconds: 3 + failureThreshold: 3 + periodSeconds: 6 + successThreshold: 1 + timeoutSeconds: 5 + } + ] + : [ + { + type: 'liveness' + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: { + port: 8080 + } + timeoutSeconds: 1 + } + { + type: 'readiness' + failureThreshold: 48 + initialDelaySeconds: 3 + periodSeconds: 5 + successThreshold: 1 + tcpSocket: { + port: 8080 + } + timeoutSeconds: 5 + } + ] } ] revisionSuffix: revisionSuffix From 52859c74d7b0be3b6eaf57b0a41c942a77730992 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Mon, 1 Jul 2024 17:25:01 +0200 Subject: [PATCH 3/5] Disable health check configuration when using initial QuickStart image --- cloud-infrastructure/modules/container-app.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud-infrastructure/modules/container-app.bicep b/cloud-infrastructure/modules/container-app.bicep index e9646948d..b6ed1dff2 100644 --- a/cloud-infrastructure/modules/container-app.bicep +++ b/cloud-infrastructure/modules/container-app.bicep @@ -94,7 +94,7 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-02-preview' = { memory: memory } env: environmentVariables - probes: hasProbesEndpoint + probes: hasProbesEndpoint && containerImageTag != 'initial' // The quickstart image does not have liveness and readiness probes ? [ { type: 'Liveness' From 9e463f19f07204610ea9783caf3ce857ec36d657 Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Mon, 1 Jul 2024 16:23:03 +0200 Subject: [PATCH 4/5] Update YARP configuration to route only /api/account-management-api/ traffic to AccountManagement self-contained system --- application/AppGateway/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/AppGateway/appsettings.json b/application/AppGateway/appsettings.json index e8abadd9b..2aa5436d6 100644 --- a/application/AppGateway/appsettings.json +++ b/application/AppGateway/appsettings.json @@ -14,7 +14,7 @@ "account-management-api": { "ClusterId": "account-management-api", "Match": { - "Path": "/api/{**catch-all}" + "Path": "/api/account-management-api/{**catch-all}" }, "Transforms": [ { From cd5799a3db3371494942391e54dddf1193f4e46d Mon Sep 17 00:00:00 2001 From: Thomas Jespersen Date: Mon, 1 Jul 2024 16:32:46 +0200 Subject: [PATCH 5/5] Add custom request transformation in AppGateway that blocks all public traffic to */internal-api/* --- application/AppGateway/Program.cs | 5 +++++ .../Transformations/BlockInternalApiTransform.cs | 16 ++++++++++++++++ .../Transformations/ManagedIdentityTransform.cs | 5 ++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 application/AppGateway/Transformations/BlockInternalApiTransform.cs diff --git a/application/AppGateway/Program.cs b/application/AppGateway/Program.cs index 4f86b5086..58778749b 100644 --- a/application/AppGateway/Program.cs +++ b/application/AppGateway/Program.cs @@ -32,6 +32,11 @@ ); } +builder.Services.AddSingleton(); +reverseProxyBuilder.AddTransforms(context => + context.RequestTransforms.Add(context.Services.GetRequiredService()) +); + builder.Services.AddNamedBlobStorages(builder, ("avatars-storage", "AVATARS_STORAGE_URL")); builder.WebHost.UseKestrel(option => option.AddServerHeader = false); diff --git a/application/AppGateway/Transformations/BlockInternalApiTransform.cs b/application/AppGateway/Transformations/BlockInternalApiTransform.cs new file mode 100644 index 000000000..da8286c09 --- /dev/null +++ b/application/AppGateway/Transformations/BlockInternalApiTransform.cs @@ -0,0 +1,16 @@ +using Yarp.ReverseProxy.Transforms; + +namespace PlatformPlatform.AppGateway.Transformations; + +public class BlockInternalApiTransform : RequestTransform +{ + public override async ValueTask ApplyAsync(RequestTransformContext context) + { + if (context.HttpContext.Request.Path.Value?.Contains("/internal-api/", StringComparison.OrdinalIgnoreCase) == true) + { + context.HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; + context.HttpContext.Response.ContentType = "text/plain"; + await context.HttpContext.Response.WriteAsync("Access to internal API is forbidden."); + } + } +} diff --git a/application/AppGateway/Transformations/ManagedIdentityTransform.cs b/application/AppGateway/Transformations/ManagedIdentityTransform.cs index 13e770510..e625f5fb6 100644 --- a/application/AppGateway/Transformations/ManagedIdentityTransform.cs +++ b/application/AppGateway/Transformations/ManagedIdentityTransform.cs @@ -8,7 +8,10 @@ public class ManagedIdentityTransform(TokenCredential credential) { protected override string? GetValue(RequestTransformContext context) { - if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars")) return null; + if (!context.HttpContext.Request.Path.StartsWithSegments("/avatars", StringComparison.OrdinalIgnoreCase)) + { + return null; + } var tokenRequestContext = new TokenRequestContext(["https://storage.azure.com/.default"]); var token = credential.GetToken(tokenRequestContext, context.HttpContext.RequestAborted);