diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7a78a91a6..62ef74ae7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,18 +1,30 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/dotnet/.devcontainer/base.Dockerfile - -# [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal -ARG VARIANT="7.0-bullseye-slim" -FROM mcr.microsoft.com/vscode/devcontainers/dotnet:dev-${VARIANT} - -# [Choice] Node.js version: none, lts/*, 18, 16, 14 -ARG NODE_VERSION="none" -RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends moby-engine moby-cli moby-build moby-run - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 - +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/dotnet/.devcontainer/base.Dockerfile + +# [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal +ARG VARIANT="7.0-bullseye-slim" +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:dev-${VARIANT} + +# [Choice] Node.js version: none, lts/*, 18, 16, 14 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends moby-engine moby-cli moby-build moby-run + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + +RUN sudo apt-get update && \ + sudo apt-get -y install ca-certificates curl gnupg && \ + sudo install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ + sudo chmod a+r /etc/apt/keyrings/docker.gpg && \ + echo \ + "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ + "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \ + sudo apt-get update && \ + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + RUN dotnet dev-certs https \ No newline at end of file diff --git a/src/AzureIoTHub.Portal.Application/Providers/IDeviceRegistryProvider.cs b/src/AzureIoTHub.Portal.Application/Providers/IDeviceRegistryProvider.cs index f932c4e69..0c0229387 100644 --- a/src/AzureIoTHub.Portal.Application/Providers/IDeviceRegistryProvider.cs +++ b/src/AzureIoTHub.Portal.Application/Providers/IDeviceRegistryProvider.cs @@ -5,9 +5,9 @@ namespace AzureIoTHub.Portal.Application.Providers { using System.Threading; using System.Threading.Tasks; - using AzureIoTHub.Portal.Models.v10; using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; + using Shared.Models.v10; public interface IDeviceRegistryProvider { @@ -38,7 +38,7 @@ public interface IDeviceRegistryProvider /// /// The device identifier. /// The device model id. - Task GetEnrollmentCredentialsAsync(string deviceId, string modelId); + Task GetEnrollmentCredentialsAsync(string deviceId, string modelId); Task DeleteEnrollmentGroupAsync(EnrollmentGroup enrollmentGroup, CancellationToken cancellationToken); diff --git a/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs b/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs index 57474977f..35887ebf8 100644 --- a/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/AWS/IAWSExternalDeviceService.cs @@ -20,5 +20,7 @@ public interface IAWSExternalDeviceService Task GetDeviceShadow(string deviceName); Task UpdateDeviceShadow(UpdateThingShadowRequest shadow); + + } } diff --git a/src/AzureIoTHub.Portal.Application/Services/IDeviceService.cs b/src/AzureIoTHub.Portal.Application/Services/IDeviceService.cs index b34735473..68822d04d 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IDeviceService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IDeviceService.cs @@ -35,7 +35,7 @@ Task> GetDevices( Task DeleteDevice(string deviceId); - Task GetCredentials(string deviceId); + Task GetCredentials(string deviceId); Task> GetDeviceTelemetry(string deviceId); diff --git a/src/AzureIoTHub.Portal.Application/Services/IEdgeDevicesService.cs b/src/AzureIoTHub.Portal.Application/Services/IEdgeDevicesService.cs index 51785a979..2a02c8334 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IEdgeDevicesService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IEdgeDevicesService.cs @@ -33,5 +33,7 @@ Task> GetEdgeDevicesPage( Task ExecuteModuleCommand(string deviceId, string moduleName, string commandName); Task> GetAvailableLabels(); + + Task GetEdgeDeviceEnrollementScript(string deviceId, string templateName); } } diff --git a/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs b/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs index 497adf033..5d17b76f9 100644 --- a/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs +++ b/src/AzureIoTHub.Portal.Application/Services/IExternalDeviceService.cs @@ -9,6 +9,7 @@ namespace AzureIoTHub.Portal.Application.Services using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Shared; using AzureIoTHub.Portal.Domain.Shared; + using Shared.Models.v10; public interface IExternalDeviceService { @@ -21,10 +22,13 @@ public interface IExternalDeviceService Task GetDeviceTwin(string deviceId); Task GetDeviceTwinWithModule(string deviceId); + Task GetDeviceTwinWithEdgeHubModule(string deviceId); Task CreateDeviceWithTwin(string deviceId, bool isEdge, Twin twin, DeviceStatus isEnabled); + Task CreateEdgeDevice(string deviceId); + Task UpdateDevice(Device device); Task UpdateDeviceTwin(Twin twin); @@ -64,9 +68,9 @@ Task> GetAllEdgeDevice( Task GetConcentratorsCount(); - Task GetEnrollmentCredentials(string deviceId); + Task GetDeviceCredentials(string deviceId); - Task GetEdgeDeviceCredentials(string edgeDeviceId); + Task GetEdgeDeviceCredentials(IoTEdgeDevice device); Task RetrieveLastConfiguration(Twin twin); @@ -75,5 +79,9 @@ Task> GetAllEdgeDevice( Task> GetAllGatewayID(); Task> GetDevicesToExport(); + + Task CreateEnrollementScript(string template, Domain.Entities.EdgeDevice device); + + Task RemoveDeviceCredentials(IoTEdgeDevice device); } } diff --git a/src/AzureIoTHub.Portal.Client/Pages/Devices/ConnectionStringDialog.razor b/src/AzureIoTHub.Portal.Client/Pages/Devices/ConnectionStringDialog.razor index 97b90151a..bf944f6c1 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/Devices/ConnectionStringDialog.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/Devices/ConnectionStringDialog.razor @@ -41,7 +41,7 @@ [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; [Parameter] public string deviceId { get; set; } = default!; - private EnrollmentCredentials credentials = new(); + private SymmetricCredentials credentials = new(); protected override async Task OnInitializedAsync() { diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/ConnectionStringDialog.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/ConnectionStringDialog.razor index 2277991f0..dc3e2b363 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/ConnectionStringDialog.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/ConnectionStringDialog.razor @@ -1,47 +1,86 @@ @using AzureIoTHub.Portal.Models.v10 +@using AzureIoTHub.Portal.Shared.Constants; @inject ClipboardService ClipboardService @inject IEdgeDeviceClientService EdgeDeviceClientService +@inject PortalSettings Portal - - - - + + + + + @if (Portal.CloudProvider.Equals(CloudProviders.Azure)) + { Service Endpoint - + Registration Id - + Scope Id - + Symmetric Key - - - - - - Cancel - - + } + else if (Portal.CloudProvider.Equals(CloudProviders.AWS)) + { + + @if (string.IsNullOrEmpty(EnrollementScriptCommand)) + { + Quick connect +
+ + Quiclky connect your Edge device on the platform by executing one command. + +
+ Get the magic command + } + else + { +
+ Operating system + + Debian 11 (Bulleseye) + +
+ + Copy this command line above and paste it into the device prompt.
+ Note that you should have administrative rights on the device to execute the command. +
+
+ + } +
+ } + + + + + + OK + + @code { [CascadingParameter] public Error Error { get; set; } = default!; - + [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; [Parameter] public string deviceId { get; set; } = default!; - private EnrollmentCredentials Credentials = new EnrollmentCredentials(); + private SymmetricCredentials Credentials = new SymmetricCredentials(); + + public string EnrollementScriptCommand { get; set; } = default!; + + public string templateName { get; set; } = "debian_11_bullseye"; protected override async Task OnInitializedAsync() { @@ -53,10 +92,16 @@ catch (ProblemDetailsException exception) { Error?.ProcessProblemDetails(exception); - Cancel(); + Close(); } } + private async Task GetEnrollmentScriptCommand() + { + var url = await EdgeDeviceClientService.GetEnrollmentScriptUrl(deviceId, templateName); + + EnrollementScriptCommand = $"curl -s {url} | bash"; + } - void Cancel() => MudDialog.Cancel(); + void Close() => MudDialog.Cancel(); } diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor index 8f5d56556..eaab7cea3 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor @@ -7,6 +7,7 @@ @attribute [Authorize] @inject PortalSettings Portal @inject ISnackbar Snackbar +@inject PortalSettings Portal @inject NavigationManager NavigationManager @inject IEdgeDeviceClientService EdgeDeviceClientService @inject IEdgeModelClientService EdgeModelClientService @@ -20,9 +21,10 @@ - + - @(string.IsNullOrEmpty(EdgeDevice.DeviceName) ? EdgeDevice.DeviceId : EdgeDevice.DeviceName) + Model: @edgeModel?.Name + @(string.IsNullOrEmpty(EdgeDevice?.DeviceName) ? EdgeDevice?.DeviceId : EdgeDevice?.DeviceName) @@ -90,12 +92,16 @@ - + @if (Portal.CloudProvider.Equals(CloudProviders.Azure)) + { + + + } diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor index 6654e0c6a..64f43ab4b 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeDevices/EdgeDeviceDetailPage.razor @@ -397,7 +397,7 @@ { var parameters = new DialogParameters { { nameof(ConnectionStringDialog.deviceId), this.deviceId } }; - _ = await DialogService.Show("Edge Device Connection String", parameters).Result; + _ = await DialogService.Show("Connect Edge device", parameters).Result; } public async Task ShowDeleteModal() diff --git a/src/AzureIoTHub.Portal.Client/Services/DeviceClientService.cs b/src/AzureIoTHub.Portal.Client/Services/DeviceClientService.cs index ccbe717c7..3643232d7 100644 --- a/src/AzureIoTHub.Portal.Client/Services/DeviceClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/DeviceClientService.cs @@ -60,9 +60,9 @@ public Task SetDeviceProperties(string deviceId, IList devi return this.http.PostAsJsonAsync($"api/devices/{deviceId}/properties", deviceProperties); } - public Task GetEnrollmentCredentials(string deviceId) + public Task GetEnrollmentCredentials(string deviceId) { - return this.http.GetFromJsonAsync($"api/devices/{deviceId}/credentials")!; + return this.http.GetFromJsonAsync($"api/devices/{deviceId}/credentials")!; } public Task DeleteDevice(string deviceId) diff --git a/src/AzureIoTHub.Portal.Client/Services/EdgeDeviceClientService.cs b/src/AzureIoTHub.Portal.Client/Services/EdgeDeviceClientService.cs index 36312cf7b..8fd6146f7 100644 --- a/src/AzureIoTHub.Portal.Client/Services/EdgeDeviceClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/EdgeDeviceClientService.cs @@ -44,9 +44,14 @@ public Task DeleteDevice(string deviceId) return this.http.DeleteAsync($"api/edge/devices/{deviceId}"); } - public Task GetEnrollmentCredentials(string deviceId) + public Task GetEnrollmentCredentials(string deviceId) { - return this.http.GetFromJsonAsync($"api/edge/devices/{deviceId}/credentials")!; + return this.http.GetFromJsonAsync($"api/edge/devices/{deviceId}/credentials")!; + } + + public Task GetEnrollmentScriptUrl(string deviceId, string templateName) + { + return this.http.GetStringAsync($"api/edge/devices/{deviceId}/enrollementScript/{templateName}")!; } public async Task> GetEdgeDeviceLogs(string deviceId, IoTEdgeModule edgeModule) diff --git a/src/AzureIoTHub.Portal.Client/Services/IDeviceClientService.cs b/src/AzureIoTHub.Portal.Client/Services/IDeviceClientService.cs index 3a6f995ed..33ea49404 100644 --- a/src/AzureIoTHub.Portal.Client/Services/IDeviceClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/IDeviceClientService.cs @@ -23,7 +23,7 @@ public interface IDeviceClientService Task SetDeviceProperties(string deviceId, IList deviceProperties); - Task GetEnrollmentCredentials(string deviceId); + Task GetEnrollmentCredentials(string deviceId); Task DeleteDevice(string deviceId); diff --git a/src/AzureIoTHub.Portal.Client/Services/IEdgeDeviceClientService.cs b/src/AzureIoTHub.Portal.Client/Services/IEdgeDeviceClientService.cs index 804edf522..8167a37dd 100644 --- a/src/AzureIoTHub.Portal.Client/Services/IEdgeDeviceClientService.cs +++ b/src/AzureIoTHub.Portal.Client/Services/IEdgeDeviceClientService.cs @@ -20,7 +20,9 @@ public interface IEdgeDeviceClientService Task DeleteDevice(string deviceId); - Task GetEnrollmentCredentials(string deviceId); + Task GetEnrollmentCredentials(string deviceId); + + Task GetEnrollmentScriptUrl(string deviceId, string templateName); Task> GetEdgeDeviceLogs(string deviceId, IoTEdgeModule edgeModule); diff --git a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj index d73ae7985..108f0ade3 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj +++ b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj @@ -1,11 +1,9 @@ - net7.0 enable enable - @@ -31,7 +29,6 @@ - PreserveNewest @@ -89,8 +86,8 @@ PreserveNewest - + @@ -104,7 +101,6 @@ - @@ -116,6 +112,7 @@ + @@ -147,14 +144,10 @@ - - - - \ No newline at end of file diff --git a/src/AzureIoTHub.Portal.Infrastructure/EdgeEnrollementScripts/AWS/debian_11_bullseye b/src/AzureIoTHub.Portal.Infrastructure/EdgeEnrollementScripts/AWS/debian_11_bullseye new file mode 100644 index 000000000..281dc0a44 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/EdgeEnrollementScripts/AWS/debian_11_bullseye @@ -0,0 +1,57 @@ +#!/bin/sh + +## Install all dthe dependencies +apt-get update && \ + apt install -y systemd default-jdk unzip curl sudo + +## Create the greengrass configuration +mkdir -p /etc/greengrass/certs /etc/greengrass/config +useradd --system --create-home ggc_user +groupadd --system ggc_group + +cat > /etc/greengrass/certs/device.pem.crt << EOF +%CERTIFICATE% +EOF + +cat > /etc/greengrass/certs/private.pem.key << EOF +%PRIVATE_KEY% +EOF + +curl -o /etc/greengrass/certs/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem + +cat > /etc/greengrass/config/config.yaml << EOF +--- +system: + certificateFilePath: "/etc/greengrass/certs/device.pem.crt" + privateKeyPath: "/etc/greengrass/certs/private.pem.key" + rootCaPath: "/etc/greengrass/certs/AmazonRootCA1.pem" + rootpath: "/greengrass/v2" + thingName: "%THING_NAME%" +services: + aws.greengrass.Nucleus: + componentType: "NUCLEUS" + version: "nucleus-version" + configuration: + awsRegion: "%REGION%" + iotRoleAlias: "GreengrassCoreTokenExchangeRoleAlias" + iotDataEndpoint: "%DATA_ENDPOINT%" + iotCredEndpoint: "%CREDENTIALS_ENDPOINT%" +EOF + +chown -R ggc_user:ggc_group /etc/greengrass +chmod 640 /etc/greengrass/ -R + +## Download the AWS IoT Greengrass Core software +curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip +jarsigner -verify -certs -verbose greengrass-nucleus-latest.zip +unzip greengrass-nucleus-latest.zip -d GreengrassInstaller && rm greengrass-nucleus-latest.zip + +## Install the AWS IoT Greengrass Core software +java -Droot="/greengrass/v2" -Dlog.store=FILE \ + -jar ./GreengrassInstaller/lib/Greengrass.jar \ + --init-config /etc/greengrass/config/config.yaml \ + --component-default-user ggc_user:ggc_group \ + --setup-system-service true + +## Remove the installer directory +rm -rf ./GreengrassInstaller \ No newline at end of file diff --git a/src/AzureIoTHub.Portal.Infrastructure/Helpers/EdgeEnrollementHelper.cs b/src/AzureIoTHub.Portal.Infrastructure/Helpers/EdgeEnrollementHelper.cs new file mode 100644 index 000000000..c72f3961f --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Helpers/EdgeEnrollementHelper.cs @@ -0,0 +1,24 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Infrastructure.Helpers +{ + using System.Reflection; + + public class EdgeEnrollementHelper : IEdgeEnrollementHelper + { + public string? GetEdgeEnrollementTemplate(string templateName) + { + var currentAssembly = Assembly.GetExecutingAssembly(); + + using var resourceStream = currentAssembly.GetManifestResourceStream($"{currentAssembly.GetName().Name}.EdgeEnrollementScripts.{templateName}"); + + if (resourceStream == null) + return null; + + using var streamReader = new StreamReader(resourceStream); + + return streamReader.ReadToEnd(); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Helpers/IEdgeEnrollementHelper.cs b/src/AzureIoTHub.Portal.Infrastructure/Helpers/IEdgeEnrollementHelper.cs new file mode 100644 index 000000000..ce69fbd61 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Helpers/IEdgeEnrollementHelper.cs @@ -0,0 +1,10 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Infrastructure.Helpers +{ + public interface IEdgeEnrollementHelper + { + string GetEdgeEnrollementTemplate(string templateName); + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs index 2cd8457bb..f7eb30676 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -4,10 +4,11 @@ namespace AzureIoTHub.Portal.Infrastructure { using Domain.Entities; + using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; - public class PortalDbContext : DbContext + public class PortalDbContext : DbContext, IDataProtectionKeyContext { public DbSet DeviceModelProperties { get; set; } public DbSet DeviceTags { get; set; } @@ -22,11 +23,12 @@ public class PortalDbContext : DbContext public DbSet Concentrators { get; set; } public DbSet LoRaDeviceTelemetry { get; set; } public DbSet