From 3a651a3f63f2c17ad81c8856a2f1423a40907bc3 Mon Sep 17 00:00:00 2001 From: Torstein Lundervold Nesheim Date: Fri, 10 Jun 2022 12:05:50 +0200 Subject: [PATCH 1/3] Fix dockerization of backend --- backend/Dockerfile | 3 ++ .../ConfigurationBuilderExtensions.cs | 33 +++++++++++++++++++ backend/api/Program.cs | 7 +++- backend/backend.sln | 5 +++ docker-compose.yml | 14 ++++---- 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 backend/api/Configurations/ConfigurationBuilderExtensions.cs diff --git a/backend/Dockerfile b/backend/Dockerfile index 3dc8890d8..4a978f5c2 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -16,6 +16,9 @@ COPY --from=build /app ./ EXPOSE 8000 # Runtime user change to non-root for added security +RUN useradd -ms /bin/bash --uid 1001 isar +RUN chown -R 1001 /app +RUN chmod 755 /app USER 1001 ENTRYPOINT ["dotnet", "api.dll", "--urls=http://0.0.0.0:8000"] diff --git a/backend/api/Configurations/ConfigurationBuilderExtensions.cs b/backend/api/Configurations/ConfigurationBuilderExtensions.cs new file mode 100644 index 000000000..8ed6a198e --- /dev/null +++ b/backend/api/Configurations/ConfigurationBuilderExtensions.cs @@ -0,0 +1,33 @@ +namespace Api.Configurations +{ + public static class ConfigurationBuilderExtensions + { + /// + /// Creates the AZURE_CLIENT_ID and AZURE_TENANT_ID configuration values for the + /// Environment Credentials + /// used by the application when dockerized. + /// + /// + /// + public static void AddAzureEnvironmentVariables(this WebApplicationBuilder builder) + { + string? clientId = builder.Configuration + .GetSection("AzureAd") + .GetValue("ClientId"); + if (clientId is not null) + { + Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId); + Console.WriteLine("'AZURE_CLIENT_ID' set to " + clientId); + } + + string? tenantId = builder.Configuration + .GetSection("AzureAd") + .GetValue("TenantId"); + if (clientId is not null) + { + Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId); + Console.WriteLine("'AZURE_TENANT_ID' set to " + tenantId); + } + } + } +} diff --git a/backend/api/Program.cs b/backend/api/Program.cs index 758aa99bf..041bc2c50 100644 --- a/backend/api/Program.cs +++ b/backend/api/Program.cs @@ -11,6 +11,7 @@ using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); +builder.AddAzureEnvironmentVariables(); builder.Services.AddDbContext( options => options.UseInMemoryDatabase("flotilla") @@ -56,9 +57,13 @@ } ); +// The ExcludeSharedTokenCacheCredential option is a recommended workaround by Azure for dockerization +// See https://github.com/Azure/azure-sdk-for-net/issues/17052 builder.Configuration.AddAzureKeyVault( new Uri(builder.Configuration.GetSection("KeyVault")["VaultUri"]), - new DefaultAzureCredential() + new DefaultAzureCredential( + new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true } + ) ); var app = builder.Build(); diff --git a/backend/backend.sln b/backend/backend.sln index 29c8c42d6..8c19b1bec 100644 --- a/backend/backend.sln +++ b/backend/backend.sln @@ -10,6 +10,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.test", "api.test\api.te {8AB478C5-C4C7-4CDC-A691-F99C4A5C407A} = {8AB478C5-C4C7-4CDC-A691-F99C4A5C407A} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DFD9CB9F-ADDF-4167-A11C-E8EE83EE0548}" + ProjectSection(SolutionItems) = preProject + Dockerfile = Dockerfile + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docker-compose.yml b/docker-compose.yml index d5a8b4411..adf2dbec6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,20 @@ -version: '3.7' +version: "3.7" services: frontend: build: frontend ports: - - '3001:3001' + - "3001:3001" broker: build: broker ports: - - '1883:1883' - - '9001:9001' + - "1883:1883" + - "9001:9001" backend: build: backend ports: - - '8000:8000' + - "8000:8000" environment: + # Overwrites the ip address for the Mqtt service to connect to the Mqtt container: + - Mqtt__Host=broker - ASPNETCORE_ENVIRONMENT=Development - - AzureAd__ClientId=ea4c7b92-47b3-45fb-bd25-a8070f0c495c + - AZURE_CLIENT_SECRET=${FLOTILLA_CLIENT_SECRET} From 233cd0e89d6e507e29b918c93e33c15d6c7215db Mon Sep 17 00:00:00 2001 From: Torstein Lundervold Nesheim Date: Fri, 10 Jun 2022 12:11:15 +0200 Subject: [PATCH 2/3] Add docker instructions to readme for backend --- backend/README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/backend/README.md b/backend/README.md index 6ffb8975a..45917e4d5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -13,9 +13,10 @@ To set up the backend on **Linux**, install .NET for linux [here](https://docs.microsoft.com/en-us/dotnet/core/install/linux). For the configuration to be able to read secrets from the keyvault, you will need to have the client secret stored locally -in your secret manager as described in the [Configuration Section](#Configuration). +in your secret manager as described in the [Configuration Section](#Configuration). -For the MQTT client to function, the application expects a config variable named `mqtt-broker-password`, containing the password for the mqtt broker. This must either be stored in a connected keyvault or in the ASP.NET secret manager. +For the MQTT client to function, the application expects a config variable named `mqtt-broker-password`, containing the password for the mqtt broker. +This must either be stored in a connected keyvault or in the ASP.NET secret manager. ## Run @@ -30,6 +31,22 @@ To change the ports of the application and various other launch settings (such a Read more about the `launchSettings.json` file [here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0&preserve-view=true&viewFallbackFrom=aspnetcore-2.2#lsj) +### Run in Docker + +For the backend to work when dockerized, you need to have the client secret exposed as +an environment variable named `FLOTILLA_CLIENT_SECRET`. +The simplest way to do this in bash is to run + +``` +export FLOTILLA_CLIENT_SECRET=SuperSecret +``` + +To run the backend in docker, run the following command in the root folder of flotilla: + +``` +docker compose up backend --build +``` + ## Test To unit test the backend, run the following command in the backend folder: @@ -41,6 +58,7 @@ dotnet test ## Components ### MQTT Client + The MQTT client is implemented in [MqttService.cs](api/MQTT/MqttService.cs) and runs as an ASP.NET [BackgroundService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#backgroundservice-base-class). @@ -67,14 +85,14 @@ various app registrations used in development. The configuration will also read from a configured azure keyvault, which can then be accessed the same way as any other config variables. For this to work you will need to have the client secret stored locally in the secret manager as described below. -The client secret should be in the following format: +The client secret should be in the following format: + ``` "AzureAd": { "ClientSecret": "SECRET" } ``` - Any local secrets used for configuration should be added in the [ASP.NET Secret Manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=linux#secret-manager). @@ -106,5 +124,3 @@ dotnet format is used to detect naming conventions and other code-related issues ``` dotnet format --severity info ``` - - From d57b5859906be1e3f68ced2dfb35ebd9e7e93440 Mon Sep 17 00:00:00 2001 From: Torstein Lundervold Nesheim Date: Fri, 10 Jun 2022 12:11:39 +0200 Subject: [PATCH 3/3] Fix misguiding logging for ISAR mqtt callbacks --- backend/api/EventHandlers/MqttEventHandler.cs | 51 ++++++++++--------- backend/api/Services/ReportService.cs | 18 ++++--- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/backend/api/EventHandlers/MqttEventHandler.cs b/backend/api/EventHandlers/MqttEventHandler.cs index bf41aea6f..97ed618d5 100644 --- a/backend/api/EventHandlers/MqttEventHandler.cs +++ b/backend/api/EventHandlers/MqttEventHandler.cs @@ -51,15 +51,16 @@ private async void OnMissionUpdate(object? sender, MqttReceivedArgs mqttArgs) return; } - await _reportService.UpdateMissionStatus(mission.MissionId, status); + bool success = await _reportService.UpdateMissionStatus(mission.MissionId, status); - _logger.LogInformation( - "{time} - Mission {id} updated to {status} for {robot}", - mission.Timestamp, - mission.MissionId, - mission.Status, - mission.RobotId - ); + if (success) + _logger.LogInformation( + "{time} - Mission {id} updated to {status} for {robot}", + mission.Timestamp, + mission.MissionId, + mission.Status, + mission.RobotId + ); } private async void OnTaskUpdate(object? sender, MqttReceivedArgs mqttArgs) @@ -80,15 +81,16 @@ private async void OnTaskUpdate(object? sender, MqttReceivedArgs mqttArgs) return; } - await _reportService.UpdateTaskStatus(task.TaskId, status); + bool success = await _reportService.UpdateTaskStatus(task.TaskId, status); - _logger.LogInformation( - "{time} - Task {id} updated to {status} for {robot}", - task.Timestamp, - task.TaskId, - task.Status, - task.RobotId - ); + if (success) + _logger.LogInformation( + "{time} - Task {id} updated to {status} for {robot}", + task.Timestamp, + task.TaskId, + task.Status, + task.RobotId + ); } private async void OnStepUpdate(object? sender, MqttReceivedArgs mqttArgs) @@ -109,15 +111,16 @@ private async void OnStepUpdate(object? sender, MqttReceivedArgs mqttArgs) return; } - await _reportService.UpdateStepStatus(step.StepId, status); + bool success = await _reportService.UpdateStepStatus(step.StepId, status); - _logger.LogInformation( - "{time} - Step {id} updated to {status} for {robot}", - step.Timestamp, - step.StepId, - step.Status, - step.RobotId - ); + if (success) + _logger.LogInformation( + "{time} - Step {id} updated to {status} for {robot}", + step.Timestamp, + step.StepId, + step.Status, + step.RobotId + ); } } } diff --git a/backend/api/Services/ReportService.cs b/backend/api/Services/ReportService.cs index c37e7b757..b411af39a 100644 --- a/backend/api/Services/ReportService.cs +++ b/backend/api/Services/ReportService.cs @@ -81,7 +81,7 @@ public async Task> ReadAll() ); } - public async Task UpdateMissionStatus(string isarMissionId, ReportStatus reportStatus) + public async Task UpdateMissionStatus(string isarMissionId, ReportStatus reportStatus) { var report = await ReadByIsarMissionId(isarMissionId); if (report is null) @@ -90,15 +90,17 @@ public async Task UpdateMissionStatus(string isarMissionId, ReportStatus reportS "Could not update mission status for ISAR mission with id: {id} as the report was not found", isarMissionId ); - return; + return false; } report.ReportStatus = reportStatus; await _context.SaveChangesAsync(); + + return true; } - public async Task UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus) + public async Task UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus) { var task = await ReadIsarTaskById(isarTaskId); if (task is null) @@ -107,15 +109,17 @@ public async Task UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus) "Could not update task status for ISAR task with id: {id} as the task was not found", isarTaskId ); - return; + return false; } task.TaskStatus = taskStatus; await _context.SaveChangesAsync(); + + return true; } - public async Task UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus) + public async Task UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus) { var step = await ReadIsarStepById(isarStepId); if (step is null) @@ -124,12 +128,14 @@ public async Task UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus) "Could not update step status for ISAR step with id: {id} as the step was not found", isarStepId ); - return; + return false; } step.StepStatus = stepStatus; await _context.SaveChangesAsync(); + + return true; } } }