* Deploy a production ready OPC Vault to the cloud
* First round of documentation + how to deploy and create the first cert
This commit is contained in:
Martin Regen 2019-01-18 15:58:42 +01:00 коммит произвёл GitHub
Родитель 42890acd3a
Коммит 62198c68cd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 1155 добавлений и 259 удалений

Просмотреть файл

@ -1,3 +1,69 @@
## Azure Industrial IoT Services
### OPC Unified Architecture (OPC UA) Certificate Management Service
The certificate management service for OPC UA facilitates a CA certificate cloud service for OPC UA devices
based on Azure Key Vault and CosmosDB, a ASP.Net Core web application front end and a OPC UA GDS server based on .Net Standard.
The implementation follows the GDS Certificate Management Services as described in the OPC UA specification Part 12.
The CA certificates are stored in a HSM backed Azure Key Vault, which is also used to sign issued certificates.
A web management application front end and a local OPC UA GDS server allow for easy connection to the services secured by Azure AD.
### This repository contains the following:
* **ASP.Net Core Certificate Management Service** to manage certificates with Azure Key Vault and CosmosDB.
* **ASP.Net Core Sample Application** as user interface for the Certificate Management Service.
* **OPC UA .Net Standard GDS Server** for local OPC UA device connectivity to the cloud Certificate Management Service.
### Microservice Features
1. Production ready certificate microservice based on C# with ASP.Net Core 2.1.
2. Uses Azure Key Vault as CA certificate store, key pair generator and certificate signer backed by FIPS 140-2 Level 2 validated HSMs.
3. Uses Cosmos DB as application and certificate request database. Open database interface to integrate with other database services.
2. Secured by AzureAD role based access with separation of Reader, Writer, Approver and Administrator roles.
2. Exposes Rest API (with Swagger UI) to easily integrate certificate microservice in other cloud services.
7. Support for RSA certificates with a SHA256 signature and keys with a length of 2048, 3072 or 4096 bits.
8. Support to sign certificates created with new key pairs from Azure Key Vault or by using Certificate Signing Requests (CSR).
4. Key Pairs and signed certificates with extensions follow requirements and guidelines as specified in the OPC UA GDS Certificate Management Services, Part 12.
9. The CA has full CRL support with revocation of unregistered OPC UA applications.
5. Uses on behalf tokens to access Azure Key Vault to validate user permissions at KeyVault level in addition to the validation at the microservice Rest API.
8. Busines logic ensures secure workflow with assigned user roles and the validation of certificate requests against the application database.
9. Follows Microsoft SDL guidelines for public-key infrastructure.
5. Leverages OPC UA .NetStandard GDS Server Common libraries.
13. Uses Azure Key Vault versioning and auditing to track CA certificate access and CRL history.
### Web Certificate Management Sample Features
5. Sample code is based on the certificate management microservice Rest API using C# with ASP.Net Core 2.1.
8. Workflow to secure a OPC UA application with a CA signed certificate: Register an OPC UA application, request a certificate or key pair, generate the signed certificate and download it.
7. Secure workflow to unregister and revoke a OPC UA application including CRL updates.
5. Forms to manage OPC UA applications and certificate requests.
8. CA certificate management for the Administrator role to configure CA cert lifetime and subject name.
9. Renewal of a CA certificates.
8. Create key pairs and sign certificates with a CSR validated with application database information.
11. Upload CSR for signing requests as file or base64 string.
9. Binary and base64 download of certificates and keys as PFX, PEM and DER.
10. Issues consolidated CRL updates for multiple unregistered applications in a single step.
11. Accesses the microservice on behalf of the user to be able to execute protected functions in Azure Key Vault (e.g. signing rights for Approver).
### On premise Global Discovery Server (GDS) with cloud integration
5. Based on the GDS server common library of the OPC UA .NetStandard SDK.
6. Implements OPC UA Discovery and Certificate management services by connecting to the microservice.
7. Executes in a docker container or as a .Net Core 2.0 application on Windows or Linux.
8. Implements namespace of OPC UA GDS Discovery and Certificate Management Services V1.04, Part 12.
6. **Note:** At this time the server can only act in a reader role with limited functionality due
10. to the lack of user OAUTH2 authentication support in the .NetStandard SDK.
11. For development purposes and testing, the AzureAD registration can be enabled for a 'Writer' role to allow to create certificate requests and to update applications,
but this configuration shall not be used in production deployments.
## [Build and Deploy](docs/howto-deploy-services.md) the service to Azure
The documentation how to build and deploy the service is [here](docs/howto-deploy-services.md).
<!---
## [Build and Run](docs/howto-run-services-locally.md) the services locally
The documentation how to build and run the service is [here](docs/howto-run-services-locally.md).
-->
# Contributing
@ -12,3 +78,32 @@ provided by the bot. You will only need to do this once across all repos using o
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
### Give Feedback
Please enter issues, bugs, or suggestions for any of the components and services as GitHub Issues [here](https://github.com/Azure/azure-iiot-opcvault-service/issues).
### Contribute
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
If you want/plan to contribute, we ask you to sign a [CLA](https://cla.microsoft.com/) (Contribution License Agreement) and follow the project 's [code submission guidelines](docs/contributing.md). A friendly bot will remind you about it when you submit a pull-request.  
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [MIT](LICENSE) License.
[azure-free]:https://azure.microsoft.com/en-us/free/
[powershell-install]:https://azure.microsoft.com/en-us/downloads/#PowerShell
[run-with-docker-url]: https://docs.microsoft.com/azure/iot-suite/iot-suite-remote-monitoring-deploy-local#run-the-microservices-in-docker
[rm-arch-url]: https://docs.microsoft.com/azure/iot-suite/iot-suite-remote-monitoring-sample-walkthrough
[postman-url]: https://www.getpostman.com
[iotedge-url]: https://github.com/Azure/iotedge
[docker-url]: https://www.docker.com/
[dotnet-install]: https://www.microsoft.com/net/learn/get-started
[vs-install-url]: https://www.visualstudio.com/downloads
[dotnetcore-tools-url]: https://www.microsoft.com/net/core#windowsvs2017

Просмотреть файл

@ -255,7 +255,8 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.App.Controllers
string message = null;
try
{
await opcVault.ApproveCertificateRequestAsync(id, false);
// TODO: call depending on auto approve setup
//await opcVault.ApproveCertificateRequestAsync(id, false);
}
catch (Exception ex)
{
@ -350,7 +351,8 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.App.Controllers
string message = null;
try
{
await opcVault.ApproveCertificateRequestAsync(id, false);
// TODO: call depending on auto approve setup
//await opcVault.ApproveCertificateRequestAsync(id, false);
}
catch (Exception ex)
{

Просмотреть файл

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
@ -10,12 +10,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.5.1" />
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="Autofac" Version="4.8.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.3.1" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="2.1.1" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.1" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.354.23" />
</ItemGroup>

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
@ -19,6 +19,7 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.App
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseApplicationInsights()
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
@ -28,7 +29,7 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.App
$"{builtConfig["KeyVault"]}",
builtConfig["AzureAD:ClientId"],
builtConfig["AzureAD:ClientSecret"],
new PrefixKeyVaultSecretManager("OpcVault.App")
new PrefixKeyVaultSecretManager("App")
);
}
})

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
@ -57,7 +57,7 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.App
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddApplicationInsightsTelemetry();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
;

Просмотреть файл

@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
src\Dockerfile.Windows = src\Dockerfile.Windows
LICENSE = LICENSE
project.props = project.props
README.md = README.md
version.props = version.props
EndProjectSection
EndProject
@ -32,6 +31,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.IIoT.OpcUa.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.IIoT.OpcUa.Api.Vault", "api-csharp\Microsoft.Azure.IIoT.OpcUa.Api.Vault.csproj", "{A887CAA1-2C60-4E3A-91F8-DB60BBE4CC26}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E9C6A792-1D70-4DE9-B5FB-8D8BCAA223EE}"
ProjectSection(SolutionItems) = preProject
docs\howto-deploy-services.md = docs\howto-deploy-services.md
docs\howto-run-services-locally.md = docs\howto-run-services-locally.md
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{A3FF1EEF-11DA-470B-9C98-B043B3CB4DE7}"
ProjectSection(SolutionItems) = preProject
deploy\.gitignore = deploy\.gitignore
deploy\deploy.ps1 = deploy\deploy.ps1
deploy\KeyVault.Secret.Groups.json = deploy\KeyVault.Secret.Groups.json
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

6
deploy/.gitignore поставляемый
Просмотреть файл

@ -1,3 +1,7 @@
.user
.app
.env
.env
*.zip
*.config
*.cmd
*.publishsettings

Просмотреть файл

@ -0,0 +1 @@
[{"Id":"Default","CertificateType":"RsaSha256ApplicationCertificateType","SubjectName":"CN=Azure Industrial IoT CA, O=Microsoft Corp.","BaseStorePath":"/default","DefaultCertificateLifetime":12,"DefaultCertificateKeySize":2048,"DefaultCertificateHashSize":256,"CACertificateLifetime":60,"CACertificateKeySize":2048,"CACertificateHashSize":256}]

Просмотреть файл

@ -6,18 +6,39 @@
Deploys an Azure Resource Manager template of choice
.PARAMETER resourceGroupName
The resource group where the template will be deployed.
The resource group where the template will be deployed.
.PARAMETER webAppName
The host name prefix of the web application.
.PARAMETER webServiceName
The host name prefix of the web application.
.PARAMETER aadConfig
The AAD configuration the template will be configured with.
.PARAMETER groupsConfig
The certificate groups configuration.
.PARAMETER autoApprove
Set the web app auto approval configuration.
.PARAMETER environment
Set the web app environment configuration. (Production,Development)
.PARAMETER interactive
Whether to run in interactive mode
#>
param(
[Parameter(Mandatory=$True)] [string] $resourceGroupName,
[string] $webAppName = $null,
[string] $webServiceName = $null,
$aadConfig = $null,
[string] $groupsConfig = $null,
[string] $autoApprove = "false",
[string] $environment = "Production",
$interactive = $true
)
@ -53,19 +74,14 @@ $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
# Register RPs
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.web" | Out-Null
#Register-AzureRmResourceProvider -ProviderNamespace "microsoft.compute" | Out-Null
# Set admin password
$adminPassword = CreateRandomPassword
$templateParameters = @{
# adminPassword = $adminPassword
}
$templateParameters = @{ }
try {
# Try set branch name as current branch
$output = cmd /c "git rev-parse --abbrev-ref HEAD" 2`>`&1
$branchName = ("{0}" -f $output);
Write-Host "VM deployment will use configuration from '$branchName' branch."
Write-Host "Deployment will use configuration from '$branchName' branch."
# $templateParameters.Add("branchName", $branchName)
}
catch {
@ -75,26 +91,71 @@ catch {
# Configure auth
if ($aadConfig) {
if (![string]::IsNullOrEmpty($aadConfig.Audience)) {
# $templateParameters.Add("authAudience", $aadConfig.Audience)
$templateParameters.Add("aadAudience", $aadConfig.Audience)
}
if (![string]::IsNullOrEmpty($aadConfig.ServiceId)) {
$templateParameters.Add("aadServiceId", $aadConfig.ServiceId)
}
if (![string]::IsNullOrEmpty($aadConfig.ServiceObjectId)) {
$templateParameters.Add("aadServicePrincipalId", $aadConfig.ServicePrincipalId)
}
if (![string]::IsNullOrEmpty($aadConfig.ServiceSecret)) {
$templateParameters.Add("aadServiceSecret", $aadConfig.ServiceSecret)
}
if (![string]::IsNullOrEmpty($aadConfig.ClientId)) {
# $templateParameters.Add("aadClientId", $aadConfig.ClientId)
$templateParameters.Add("aadClientId", $aadConfig.ClientId)
}
if (![string]::IsNullOrEmpty($aadConfig.ClientSecret)) {
$templateParameters.Add("aadClientSecret", $aadConfig.ClientSecret)
}
if (![string]::IsNullOrEmpty($aadConfig.ModuleId)) {
$templateParameters.Add("aadModuleId", $aadConfig.ModuleId)
}
if (![string]::IsNullOrEmpty($aadConfig.ModuleSecret)) {
$templateParameters.Add("aadModuleSecret", $aadConfig.ModuleSecret)
}
if (![string]::IsNullOrEmpty($aadConfig.TenantId)) {
# $templateParameters.Add("aadTenantId", $aadConfig.TenantId)
$templateParameters.Add("aadTenantId", $aadConfig.TenantId)
}
if (![string]::IsNullOrEmpty($aadConfig.Instance)) {
# $templateParameters.Add("aadInstance", $aadConfig.Instance)
$templateParameters.Add("aadInstance", $aadConfig.Instance)
}
if (![string]::IsNullOrEmpty($aadConfig.UserPrincipalId)) {
$templateParameters.Add("aadUserPrincipalId", $aadConfig.UserPrincipalId)
}
}
# Configure groups
if (![string]::IsNullOrEmpty($groupsConfig)) {
$templateParameters.Add("groupsConfig", $groupsConfig)
}
# Set website name
if ($interactive) {
$webAppName = Read-Host "Please specify a website name"
if (![string]::IsNullOrEmpty($webAppName)) {
$templateParameters.Add("webAppName", $webAppName)
}
# Set web app site name
if ($interactive -and [string]::IsNullOrEmpty($webAppName)) {
$webAppName = Read-Host "Please specify a web applications site name"
}
if (![string]::IsNullOrEmpty($webAppName)) {
$templateParameters.Add("webAppName", $webAppName)
}
# Set web service site name
if ($interactive -and [string]::IsNullOrEmpty($webServiceName)) {
$webServiceName = Read-Host "Please specify a web service site name"
}
if (![string]::IsNullOrEmpty($webServiceName)) {
$templateParameters.Add("webServiceName", $webServiceName)
}
# Configure web app auto approve
if (![string]::IsNullOrEmpty($autoApprove)) {
$templateParameters.Add("autoApprove", $autoApprove)
}
# Configure web app environment
if (![string]::IsNullOrEmpty($environment)) {
$templateParameters.Add("environment", $environment)
}
# Start the deployment
@ -105,30 +166,27 @@ $deployment = New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGro
$webAppPortalUrl = $deployment.Outputs["webAppPortalUrl"].Value
$webAppServiceUrl = $deployment.Outputs["webAppServiceUrl"].Value
#$adminUser = $deployment.Outputs["adminUsername"].Value
$webAppPortalName = $deployment.Outputs["webAppPortalName"].Value
$webAppServiceName = $deployment.Outputs["webAppServiceName"].Value
if ($aadConfig -and $aadConfig.ClientObjectId) {
#
# Update client application to add reply urls required permissions.
# Update client application to add reply urls to required permissions.
#
$adClient = Get-AzureADApplication -ObjectId $aadConfig.ClientObjectId
Write-Host "Adding ReplyUrls:"
$replyUrls = New-Object System.Collections.Generic.List[System.String]
$replyUrls.Add($webAppPortalUrl)
$replyUrls.Add($webAppPortalUrl + "/oauth2-redirect.html")
Write-Host $webAppPortalUrl + "/oauth2-redirect.html"
# still connected
Set-AzureADApplication -ObjectId $aadConfig.ClientObjectId -ReplyUrls $replyUrls
$replyUrls = $adClient.ReplyUrls
# web app
$replyUrls.Add($webAppPortalUrl + "/signin-oidc")
# swagger
$replyUrls.Add($webAppServiceUrl + "/oauth2-redirect.html")
Write-Host $webAppPortalUrl"/signin-oidc"
Write-Host $webAppServiceUrl"/oauth2-redirect.html"
Set-AzureADApplication -ObjectId $aadConfig.ClientObjectId -ReplyUrls $replyUrls -HomePage $webAppPortalUrl
}
Write-Host
Write-Host "To access the web portal go to:"
Write-Host $webAppPortalUrl
Write-Host
Write-Host "To access the web service go to:"
Write-Host $webAppServiceUrl
Write-Host
#Write-Host "Use the following User and Password to log onto your VM:"
#Write-Host
#Write-Host $adminUser
#Write-Host $adminPassword
#Write-Host
if ($aadConfig -and $aadConfig.ClientObjectId) {
Set-AzureADApplication -ObjectId $aadConfig.ServiceObjectId -HomePage $webServicePortalUrl
}
Return $webAppPortalUrl, $webAppServiceUrl

Просмотреть файл

@ -2,32 +2,150 @@
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"aadTenantId": {
"type": "string",
"metadata": {
"description": "The AAD tenant identifier (GUID)"
}
},
"aadInstance": {
"type": "string",
"defaultValue": "https://login.microsoftonline.com/",
"metadata": {
"description": "Url of the AAD login page (example: https://login.microsoftonline.com/)"
}
},
"aadServiceId": {
"type": "string",
"metadata": {
"description": "AAD service application identifier (GUID)"
}
},
"aadServicePrincipalId": {
"type": "string",
"metadata": {
"description": "AAD service application principal id (GUID)"
}
},
"aadServiceSecret": {
"type": "securestring",
"metadata": {
"description": "AAD service application secret."
}
},
"aadClientId": {
"type": "string",
"metadata": {
"description": "AAD client application identifier (GUID)"
}
},
"aadClientSecret": {
"type": "securestring",
"metadata": {
"description": "AAD client application secret."
}
},
"aadModuleId": {
"type": "string",
"metadata": {
"description": "AAD module application identifier (GUID)"
}
},
"aadModuleSecret": {
"type": "securestring",
"metadata": {
"description": "AAD module application secret."
}
},
"aadAudience": {
"type": "string",
"defaultValue": "[parameters('aadServiceId')]",
"metadata": {
"description": "Audience to validate token audience against."
}
},
"aadTrustedIssuer": {
"type": "string",
"defaultValue": "[concat('https://sts.windows.net/', parameters('aadTenantId'))]",
"metadata": {
"description": "Audience to validate token audience against."
}
},
"aadUserPrincipalId": {
"type": "string",
"metadata": {
"description": "The user principal id managing the vault. (GUID)"
}
},
"webAppName": {
"type": "string",
"metadata": {
"description": "Base name of the resource such as web app name and app service plan "
"description": "Web app base name."
},
"minLength": 2
},
"webServiceName": {
"type": "string",
"metadata": {
"description": "Web service base name."
},
"minLength": 2
},
"groupsConfig": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "Default certificate group configuration. (JSON)"
}
},
"autoApprove": {
"type": "string",
"defaultValue": "false",
"metadata": {
"description": "Preset service for certificate auto approval."
}
},
"environment": {
"type": "string",
"defaultValue": "Production",
"metadata": {
"description": "Preset web app environment."
}
}
},
"variables": {
"tenantId": "[subscription().tenantId]",
"randomSuffix": "[take(uniqueString(subscription().subscriptionId, resourceGroup().id, parameters('webAppName')), 5)]",
"groupPrefix": "[take(resourceGroup().name, 8)]",
"randomSuffix": "[take(uniqueString(subscription().subscriptionId, resourceGroup().id, resourceGroup().name), 5)]",
"applocation": "[resourceGroup().location]",
"keyVaultName": "[concat('vault-', variables('randomSuffix'))]",
"keyVaultName": "[concat(variables('groupPrefix'), '-', variables('randomSuffix'))]",
"vaultSku": "Premium",
"enabledForDeployment": true,
"enabledForTemplateDeployment": false,
"enableVaultForVolumeEncryption": false,
"webAppSku": "S1",
"webAppPortalName": "[concat(parameters('webAppName'), '-app')]",
"webAppServiceName": "[concat(parameters('webAppName'), '-service')]",
"appServicePlanName": "[concat('AppServicePlan-', parameters('webAppName'))]",
"documentDBName": "[concat('cosmosdb-', variables('randomSuffix'))]",
"webAppPortalName": "[parameters('webAppName')]",
"webAppServiceName": "[parameters('webServiceName')]",
"appInsightsName": "[concat(variables('groupPrefix'), '-', variables('randomSuffix'))]",
"appServicePlanName": "[concat('AppServicePlan-', resourceGroup().name)]",
"documentDBName": "[concat(variables('groupPrefix'), '-', variables('randomSuffix'))]",
"documentDBApiVersion": "2016-03-19",
"documentDBResourceId": "[resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName'))]",
"apiType": "SQL",
"offerType": "Standard"
"offerType": "Standard",
"readPermissions": [ "Get", "List" ],
"writePermissions": [ "Get", "List", "Set" ],
"signPermissions": [ "Get", "List", "Sign" ],
"createPermissions": [ "Get", "List", "Update", "Create", "Import", "Delete"],
"groupsObject": {
"secrets": [
{
"secretName": "groups",
"secretValue": "[parameters('groupsConfig')]"
}
]
}
},
"resources": [
{
@ -64,7 +182,7 @@
{
"type": "Microsoft.KeyVault/vaults",
"name": "[variables('keyVaultName')]",
"apiVersion": "2015-06-01",
"apiVersion": "2018-02-14",
"location": "[variables('applocation')]",
"tags": {
"displayName": "KeyVault"
@ -79,8 +197,67 @@
"family": "A"
},
"accessPolicies": []
},
"resources": [
{
"type": "accessPolicies",
"name": "add",
"apiVersion": "2018-02-14",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
],
"properties": {
"accessPolicies": [
{
"tenantId": "[parameters('aadTenantId')]",
"objectId": "[parameters('aadServicePrincipalId')]",
"permissions": {
"secrets": "[variables('readPermissions')]",
"certificates": "[variables('readPermissions')]"
}
},
{
"tenantId": "[parameters('aadTenantId')]",
"objectId": "[parameters('aadUserPrincipalId')]",
"permissions": {
"keys": "[variables('signPermissions')]",
"secrets": "[variables('writePermissions')]",
"certificates": "[variables('createPermissions')]"
}
}
]
}
}
]
},
{
"condition": "[not(empty(parameters('groupsConfig')))]",
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(variables('keyVaultName'), '/', variables('groupsObject').secrets[copyIndex()].secretName)]",
"apiVersion": "2018-02-14",
"properties": {
"value": "[variables('groupsObject').secrets[copyIndex()].secretValue]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
],
"copy": {
"name": "secretsCopy",
"count": "[length(variables('groupsObject').secrets)]"
}
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"name": "[concat(variables('keyVaultName'), '/', 'Service-OpcVault--CosmosDBToken')]",
"apiVersion": "2018-02-14",
"properties": {
"value": "[listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName')), variables('documentDBApiVersion')).primaryMasterKey]"
},
"dependsOn": [
"[resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName'))]",
"[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]"
]
},
{
"apiVersion": "2016-08-01",
"type": "Microsoft.Web/sites",
@ -89,10 +266,50 @@
"location": "[variables('applocation')]",
"comments": "This is the web app, also the default 'nameless' slot.",
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
"name": "[variables('webAppPortalName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"httpsOnly": true
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppPortalName'))]",
"[resourceId('Microsoft.Web/sites', variables('webAppServiceName'))]",
"Microsoft.ApplicationInsights.AzureWebSites"
],
"tags": {
"displayName": "WebAppServiceSettings"
},
"properties": {
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(concat('microsoft.insights/components/', variables('appInsightsName'))).InstrumentationKey]",
"ASPNETCORE_ENVIRONMENT": "[parameters('environment')]",
"AZUREAD__CLIENTID": "[parameters('aadClientId')]",
"AZUREAD__CLIENTSECRET": "[parameters('aadClientSecret')]",
"AZUREAD__INSTANCE": "[parameters('aadInstance')]",
"AZUREAD__TENANTID": "[parameters('aadTenantId')]",
"OPCVAULT__BASEADDRESS": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('webAppServiceName'))).hostNames[0])]",
"OPCVAULT__RESOURCEID": "[parameters('aadServiceId')]",
"OPCVAULT__AUTOAPPROVE": "[parameters('autoApprove')]",
"WEBSITE_RUN_FROM_PACKAGE": "1"
}
},
{
"apiVersion": "2015-08-01",
"name": "Microsoft.ApplicationInsights.AzureWebSites",
"type": "siteextensions",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppPortalName'))]"
],
"properties": {
}
}
]
},
{
@ -103,12 +320,68 @@
"location": "[variables('applocation')]",
"comments": "This is the web app service, also the default 'nameless' slot.",
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
"name": "[variables('webAppServiceName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"httpsOnly": true
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
],
"resources": [
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName'))]",
"[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]",
"[resourceId('Microsoft.Web/sites', variables('webAppServiceName'))]",
"Microsoft.ApplicationInsights.AzureWebSites"
],
"tags": {
"displayName": "WebAppServiceSettings"
},
"properties": {
"APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(concat('microsoft.insights/components/', variables('appInsightsName'))).InstrumentationKey]",
"ASPNETCORE_ENVIRONMENT": "[parameters('environment')]",
"KEYVAULT": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))).vaultUri]",
"OPCVAULT__KEYVAULTBASEURL": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))).vaultUri]",
"OPCVAULT__COSMOSDBENDPOINT": "[reference(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName'))).documentEndpoint]",
//"OPCVAULT__COSMOSDBTOKEN": "[listKeys(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName')), variables('documentDBApiVersion')).primaryMasterKey]",
"OPCVAULT__AUTOAPPROVE": "[parameters('autoApprove')]",
"AUTH__APPID": "[parameters('aadServiceId')]",
"AUTH__APPSECRET": "[parameters('aadServiceSecret')]",
"AUTH__AUDIENCE": "[parameters('aadAudience')]",
"AUTH__TENANTID": "[parameters('aadTenantId')]",
"AUTH__TRUSTEDISSUER": "[parameters('aadTrustedIssuer')]",
"SWAGGER__ENABLED": "true",
"SWAGGER__APPID": "[parameters('aadClientId')]",
"SWAGGER__APPSECRET": "[parameters('aadClientSecret')]",
"WEBSITE_RUN_FROM_PACKAGE": "1"
}
},
{
"apiVersion": "2015-08-01",
"name": "Microsoft.ApplicationInsights.AzureWebSites",
"type": "siteextensions",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webAppServiceName'))]"
],
"properties": {
}
}
]
},
{
"apiVersion": "2014-04-01",
"name": "[variables('appInsightsName')]",
"type": "Microsoft.Insights/components",
"location": "[resourceGroup().location]",
"properties": {
"applicationId": "[variables('appInsightsName')]"
}
}
],
"outputs": {
"webAppPortalUrl": {
@ -119,6 +392,14 @@
"type": "string",
"value": "[concat('https://', reference(concat('Microsoft.Web/sites/', variables('webAppServiceName'))).hostNames[0])]"
},
"webAppPortalName": {
"type": "string",
"value": "[variables('webAppPortalName')]"
},
"webAppServiceName": {
"type": "string",
"value": "[variables('webAppServiceName')]"
},
"resourceGroup": {
"type": "string",
"value": "[resourceGroup().name]"
@ -126,6 +407,14 @@
"tenantId": {
"type": "string",
"value": "[variables('tenantId')]"
},
"KeyVaultBaseUrl": {
"type": "string",
"value": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))).vaultUri]"
},
"CosmosDBEndpoint": {
"type": "string",
"value": "[reference(resourceId('Microsoft.DocumentDb/databaseAccounts', variables('documentDBName'))).documentEndpoint]"
}
}
}

Просмотреть файл

@ -1,12 +1,12 @@
<#
.SYNOPSIS
Deploys Industrial IoT services to Azure
Deploys the OpcVault service to Azure
.DESCRIPTION
Deploys the Industrial IoT services dependencies and optionally micro services and UI to Azure.
Deploys the OpcVault services and UI to Azure.
.PARAMETER type
The type of deployment (cloud, vm, local)
The type of deployment (cloud) - defaults to cloud
.PARAMETER resourceGroupName
Can be the name of an existing or a new resource group.
@ -20,14 +20,12 @@
.PARAMETER resourceGroupLocation
Optional, a resource group location. If specified, will try to create a new resource group in this location.
.PARAMETER withAuthentication
Whether to enable authentication - defaults to $true.
.PARAMETER withAutoApprove
Whether to enable auto approval - defaults to $false.
.PARAMETER tenantId
AD tenant to use.
.PARAMETER credentials
To support non interactive usage of script. (TODO)
#>
param(
@ -36,13 +34,12 @@ param(
[string] $resourceGroupLocation,
[string] $subscriptionName,
[string] $subscriptionId,
[string] $accountName,
$credentials,
[string] $tenantId,
[bool] $withAuthentication = $true,
[bool] $withAutoApprove = $false,
[ValidateSet("AzureCloud")] [string] $environmentName = "AzureCloud"
)
$script:credentials = $null
$script:optionIndex = 0
#*******************************************************************************************************
@ -66,7 +63,9 @@ Function SelectEnvironment() {
-ResourceManagerUrl https://management.azure.com/ `
-ManagementPortalUrl http://go.microsoft.com/fwlink/?LinkId=254433
}
$script:locations = @("West US", "North Europe", "West Europe")
# locations currently limited by Application Insights
# TODO: test "Canada Central", "Central India", "Southeast Asia")
$script:locations = @("East US", "West US 2", "North Europe", "West Europe")
}
default {
throw ("'{0}' is not a supported Azure Cloud environment" -f $script:environmentName)
@ -76,6 +75,43 @@ Function SelectEnvironment() {
$script:environmentName = $script:environment.Name
}
#*******************************************************************************************************
# Deploy a zip to web app
#*******************************************************************************************************
Function ZipDeploy()
{
Param (
[string] $resourceGroupName,
[string] $webAppName,
[string] $folderPath,
$slotParameters
)
$filePath = $folderPath + ".zip"
if(Test-path $filePath) {Remove-item $filePath}
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($folderPath, $filePath)
if(Test-path $publishFolder) {Remove-Item -Recurse -Force $publishFolder}
$profileClient = Get-AzureRmWebAppSlotPublishingProfile `
-Format WebDeploy `
-ResourceGroupName $resourceGroupName `
-Name $webAppName `
@slotParameters
$publishProfilePath = Join-Path -Path ".\" -ChildPath "$($webAppName).$($slotParameters.Slot).publishsettings"
Write-Output $profileClient | Out-File -FilePath $publishProfilePath
$profileClientXml = [xml]$profileClient
$profileClient = $profileClientXml.publishData.publishProfile[0]
$username = $profileClient.UserName
$password = $profileClient.userPWD
$apiUrl = "https://" + $profileClient.publishUrl + "/api/zipdeploy"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username, $password)))
$userAgent = "powershell/1.0"
Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -UserAgent $userAgent -Method POST -InFile $filePath -ContentType "multipart/form-data"
}
#*******************************************************************************************************
# Called in case no account is configured to let user choose the account.
#*******************************************************************************************************
@ -159,6 +195,7 @@ Function Login() {
}
if ($rmProfileLoaded) {
$script:accountName = $rmProfile.Context.Account.Id
$script:profileFile = $profileFile;
}
}
if (!$rmProfileLoaded) {
@ -174,6 +211,7 @@ Function Login() {
$reply = Read-Host -Prompt "Save user profile in $profileFile? [y/n]"
if ($reply -match "[yY]") {
Save-AzureRmContext -Path "$profileFile"
$script:profileFile = $profileFile;
}
}
}
@ -184,7 +222,8 @@ Function Login() {
Function SelectSubscription() {
$subscriptions = Get-AzureRMSubscription
if ($script:subscriptionName -ne $null -and $script:subscriptionName -ne "") {
$subscriptionId = Get-AzureRmSubscription -SubscriptionName $script:subscriptionName
$subscription = Get-AzureRmSubscription -SubscriptionName $script:subscriptionName
$subscriptionId = $subscription.Id
}
else {
$subscriptionId = $script:subscriptionId
@ -192,8 +231,8 @@ Function SelectSubscription() {
if (![string]::IsNullOrEmpty($subscriptionId)) {
if (!$subscriptions.Id.Contains($subscriptionId)) {
Write-Error ("Invalid subscription id {0}" -f $subscriptionId)
$subscriptionId = ""
Write-Error ("Invalid subscription id {0} {1}" -f $subscriptionId.Id, $script:subscriptionName)
$subscriptionId = $null
}
}
@ -389,12 +428,6 @@ Function ConnectToAzureADTenant() {
if ($script:interactive) {
# Interactive
if (!$script:tenantId) {
if (!$script:withAuthentication) {
$reply = Read-Host -Prompt "Enable authentication? [y/n]"
if ( $reply -notmatch "[yY]" ) {
return $null
}
}
$script:tenantId = SelectAzureADTenantId
}
}
@ -459,24 +492,31 @@ Function AddResourcePermission() {
#*******************************************************************************************************
Function GetRequiredPermissions() {
Param(
[string] $applicationDisplayName,
[string] $requiredDelegatedPermissions,
[string] $requiredApplicationPermissions,
[string] $requiredApplicationPermissions,
[string] $appId,
[string] $servicePrincipalName,
$servicePrincipal
)
# If we are passed the service principal we use it directly, otherwise we find it from
# the display name (which might not be unique)
if ($servicePrincipal) {
$sp = $servicePrincipal
}
else {
$sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
else
{
if ($servicePrincipalName)
{
$sp = Get-AzureADServicePrincipal -Filter "ServicePrincipalNames eq '$servicePrincipalName'"
}
if ($appId)
{
$sp = Get-AzureADServicePrincipal -Filter "AppId eq '$appId'"
}
}
$appId = $sp.AppId
$appid = $sp.AppId
$requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
$requiredAccess.ResourceAppId = $appid
$requiredAccess.ResourceAppId = $appId
$requiredAccess.ResourceAccess =
New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
@ -488,6 +528,7 @@ Function GetRequiredPermissions() {
AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles `
-requiredAccesses $requiredApplicationPermissions -permissionType "Role"
}
return $requiredAccess
}
@ -540,9 +581,9 @@ Function GetAzureADApplicationConfig() {
-Filter "identifierUris/any(uri:uri eq 'https://$tenantName/$serviceDisplayName')"
if (!$serviceAadApplication) {
$serviceAadApplication = New-AzureADApplication -DisplayName $serviceDisplayName `
-PublicClient $False -HomePage "https://localhost" `
-PublicClient $False -HomePage "https://$serviceDisplayName.azurewebsites.net" `
-IdentifierUris "https://$tenantName/$serviceDisplayName"
Write-Host "Created new AAD service application."+$serviceDisplayName
Write-Host "Created new AAD service application '$($serviceDisplayName)'."
}
$serviceServicePrincipal=Get-AzureADServicePrincipal `
-Filter "AppId eq '$($serviceAadApplication.AppId)'"
@ -556,16 +597,18 @@ Function GetAzureADApplicationConfig() {
-Filter "DisplayName eq '$clientDisplayName'"
if (!$clientAadApplication) {
$clientAadApplication = New-AzureADApplication -DisplayName $clientDisplayName `
-PublicClient $True
Write-Host "Created new AAD client application."+$clientDisplayName
-PublicClient $False -HomePage "https://$clientDisplayName.azurewebsites.net" `
-IdentifierUris "https://$tenantName/$clientDisplayName"
Write-Host "Created new AAD client application '$($clientDisplayName)'."
}
$moduleAadApplication=Get-AzureADApplication `
-Filter "DisplayName eq '$moduleDisplayName'"
if (!$moduleAadApplication) {
$moduleAadApplication = New-AzureADApplication -DisplayName $moduleDisplayName `
-PublicClient $True
Write-Host "Created new AAD Module application. "+$moduleDisplayName
-PublicClient $False -HomePage "http://localhost" `
-IdentifierUris "https://$tenantName/$moduleDisplayName"
Write-Host "Created new AAD Module application '$($moduleDisplayName)'."
}
# Find client principal
@ -582,8 +625,6 @@ Function GetAzureADApplicationConfig() {
#
try {
$user = Get-AzureADUser -ObjectId $creds.Account.Id -ErrorAction Stop
# TODO: Check whether already owner...
Add-AzureADApplicationOwner -ObjectId $serviceAadApplication.ObjectId `
-RefObjectId $user.ObjectId
Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId `
@ -612,34 +653,65 @@ Function GetAzureADApplicationConfig() {
$appRoles.Add($adminRole)
$knownApplications = New-Object System.Collections.Generic.List[System.String]
$knownApplications.Add($clientAadApplication.AppId)
$knownApplications.Add($moduleAadApplication.AppId)
$requiredResourcesAccess = `
New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Azure Key Vault" `
-requiredDelegatedPermissions "user_impersonation"
$requiredPermissions = GetRequiredPermissions -appId "cfa8b339-82a2-471a-a3c9-0fc0be7a4093" `
-requiredDelegatedPermissions "user_impersonation"
$requiredResourcesAccess.Add($requiredPermissions)
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
-requiredDelegatedPermissions "User.Read"
$requiredPermissions = GetRequiredPermissions -appId "00000002-0000-0000-c000-000000000000" `
-requiredDelegatedPermissions "User.Read"
$requiredResourcesAccess.Add($requiredPermissions)
Set-AzureADApplication -ObjectId $serviceAadApplication.ObjectId `
-RequiredResourceAccess $requiredResourcesAccess `
-KnownClientApplications $knownApplications -AppRoles $appRoles
-KnownClientApplications $knownApplications -AppRoles $appRoles `
-RequiredResourceAccess $requiredResourcesAccess
Write-Host "'$($serviceDisplayName)' updated with required resource access, app roles and known applications."
# read updated app roles for service principal
$serviceServicePrincipal=Get-AzureADServicePrincipal `
-Filter "AppId eq '$($serviceAadApplication.AppId)'"
#
# Add current user as Writer, Approver and Administrator
#
try {
$app_role_name = "Writer"
$app_role = $serviceServicePrincipal.AppRoles | Where-Object { $_.DisplayName -eq $app_role_name }
New-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId -PrincipalId $user.ObjectId -ResourceId $serviceServicePrincipal.ObjectId -Id $app_role.Id
$app_role_name = "Approver"
$app_role = $serviceServicePrincipal.AppRoles | Where-Object { $_.DisplayName -eq $app_role_name }
New-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId -PrincipalId $user.ObjectId -ResourceId $serviceServicePrincipal.ObjectId -Id $app_role.Id
$app_role_name = "Administrator"
$app_role = $serviceServicePrincipal.AppRoles | Where-Object { $_.DisplayName -eq $app_role_name }
New-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId -PrincipalId $user.ObjectId -ResourceId $serviceServicePrincipal.ObjectId -Id $app_role.Id
}
catch
{
Write-Host "User has already app roles assigned."
}
#
# Update client application to add reply urls required permissions.
#
$replyUrls = New-Object System.Collections.Generic.List[System.String]
$replyUrls.Add("urn:ietf:wg:oauth:2.0:oob")
$replyUrls.Add("https://localhost:44342/signin-oidc")
$replyUrls.Add("http://localhost:44342/signin-oidc")
$replyUrls.Add("https://localhost:58801/oauth2-redirect.html")
$replyUrls.Add("http://localhost:58801/oauth2-redirect.html")
$requiredResourcesAccess = `
New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
$requiredPermissions = GetRequiredPermissions -applicationDisplayName $serviceDisplayName `
$requiredPermissions = GetRequiredPermissions -servicePrincipal $serviceServicePrincipal `
-requiredDelegatedPermissions "user_impersonation" # "Directory.Read.All|User.Read"
$requiredResourcesAccess.Add($requiredPermissions)
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
-requiredDelegatedPermissions "User.Read"
$requiredPermissions = GetRequiredPermissions -appId "00000002-0000-0000-c000-000000000000" `
-requiredDelegatedPermissions "User.Read"
$requiredResourcesAccess.Add($requiredPermissions)
Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId `
-RequiredResourceAccess $requiredResourcesAccess -ReplyUrls $replyUrls `
-Oauth2AllowImplicitFlow $True -Oauth2AllowUrlPathMatching $True
Write-Host "'$($clientDisplayName)' updated with required resource access, reply url and implicit flow."
#
# Update module application to add reply urls required permissions.
@ -648,26 +720,42 @@ Function GetAzureADApplicationConfig() {
$replyUrls.Add("urn:ietf:wg:oauth:2.0:oob")
$requiredResourcesAccess = `
New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
$requiredPermissions = GetRequiredPermissions -applicationDisplayName $serviceDisplayName `
$requiredPermissions = GetRequiredPermissions -servicePrincipal $serviceServicePrincipal `
-requiredDelegatedPermissions "user_impersonation" # "Directory.Read.All|User.Read"
$requiredResourcesAccess.Add($requiredPermissions)
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
-requiredDelegatedPermissions "User.Read"
$requiredPermissions = GetRequiredPermissions -appId "00000002-0000-0000-c000-000000000000" `
-requiredDelegatedPermissions "User.Read"
$requiredResourcesAccess.Add($requiredPermissions)
Set-AzureADApplication -ObjectId $moduleAadApplication.ObjectId `
-RequiredResourceAccess $requiredResourcesAccess -ReplyUrls $replyUrls `
-Oauth2AllowImplicitFlow $True -Oauth2AllowUrlPathMatching $True
-Oauth2AllowImplicitFlow $False -Oauth2AllowUrlPathMatching $False
Write-Host "'$($moduleDisplayName)' updated with required resource access, reply url and implicit flow."
return [pscustomobject] @{
$serviceSecret = New-AzureADApplicationPasswordCredential -ObjectId $serviceAadApplication.ObjectId `
-CustomKeyIdentifier "Service Key" -EndDate (get-date).AddYears(2)
$clientSecret = New-AzureADApplicationPasswordCredential -ObjectId $clientAadApplication.ObjectId `
-CustomKeyIdentifier "Client Key" -EndDate (get-date).AddYears(2)
$moduleSecret = New-AzureADApplicationPasswordCredential -ObjectId $moduleAadApplication.ObjectId `
-CustomKeyIdentifier "Module Key" -EndDate (get-date).AddYears(2)
return [pscustomobject] @{
TenantId = $tenantId
Instance = $script:environment.ActiveDirectoryAuthority
Audience = $serviceAadApplication.IdentifierUris[0].ToString()
AppId = $serviceAadApplication.AppId
AppObjectId = $serviceAadApplication.ObjectId
Audience = $serviceAadApplication.AppId
ServiceId = $serviceAadApplication.AppId
ServiceSecret = $serviceSecret.Value
ServiceObjectId = $serviceAadApplication.ObjectId
ServicePrincipalId = $serviceServicePrincipal.ObjectId
ServiceDisplayName = $serviceDisplayName
ClientId = $clientAadApplication.AppId
ClientSecret = $clientSecret.Value
ClientObjectId = $clientAadApplication.ObjectId
ClientDisplayName = $clientDisplayName
ModuleId = $moduleAadApplication.AppId
ModuleSecret = $moduleSecret.Value
ModuleObjectId = $moduleAadApplication.ObjectId
ModuleDisplayName = $moduleDisplayName
UserPrincipalId = $user.ObjectId
}
}
catch {
@ -681,10 +769,6 @@ Function GetAzureADApplicationConfig() {
Write-Host "2) in the PowerShell window, type: Install-Module AzureAD"
Write-Host
$reply = Read-Host -Prompt "Continue without authentication? [y/n]"
if ($reply -match "[yY]") {
return $null
}
throw $ex
}
}
@ -697,8 +781,11 @@ Function GetOrCreateResourceGroup() {
# Registering default resource providers
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.devices" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.documentdb" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.eventhub" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.storage" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.keyvault" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.authorization" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.insights" | Out-Null
Register-AzureRmResourceProvider -ProviderNamespace "microsoft.web" | Out-Null
while ([string]::IsNullOrEmpty($script:resourceGroupName)) {
Write-Host
@ -706,7 +793,25 @@ Function GetOrCreateResourceGroup() {
}
# Create or check for existing resource group
Select-AzureRmSubscription -SubscriptionId $script:subscriptionId -Force | Out-Host
Write-Host "Select Subscription '$script:subscriptionId'"
if ((Get-AzureRmContext).Subscription.Id -ne $script:subscriptionId)
{
Enable-AzureRmContextAutosave
Add-AzureRmAccount -SubscriptionId $script:subscriptionId
Set-AzureRmContext -SubscriptionId $script:subscriptionId -Force | Out-Host
# context change required a new logon and the saved context should be updated
if ($script:profileFile)
{
$reply = Read-Host -Prompt "Save user profile in $script:profileFile? [y/n]"
if ($reply -match "[yY]") {
Save-AzureRmContext -Path "$script:profileFile"
}
}
}
else
{
Select-AzureRmSubscription -SubscriptionId $script:subscriptionId -Force | Out-Host
}
$resourceGroup = Get-AzureRmResourceGroup -Name $script:resourceGroupName `
-ErrorAction SilentlyContinue
if(!$resourceGroup) {
@ -737,34 +842,129 @@ if(![System.IO.File]::Exists($deploymentScript)) {
}
$script:interactive = $($script:credential -eq $null)
$script:subscriptionId = $null
SelectEnvironment
Login
SelectSubscription
$deleteOnErrorPrompt = GetOrCreateResourceGroup
$aadConfig = GetAzureADApplicationConfig
$aadConfig = GetAzureADApplicationConfig
$webAppName = $script:resourceGroupName + "-app"
$webServiceName = $script:resourceGroupName + "-service"
# the initial group configuration is only set once
if ($deleteOnErrorPrompt)
{
$groupsConfig = Get-Content .\KeyVault.Secret.Groups.json -Raw
}
# start the ARM deployment script
try {
Write-Host "Almost done..."
& ($deploymentScript) -resourceGroupName $script:resourceGroupName `
-interactive $script:interactive -aadConfig $aadConfig
Write-Host "Start deployment..."
$serviceUrls = & ($deploymentScript) -resourceGroupName $script:resourceGroupName `
-interactive $script:interactive -aadConfig $aadConfig `
-webAppName $webAppName -webServiceName $webServiceName `
-groupsConfig $groupsConfig -autoApprove $withAutoApprove `
-environment "Development"
Write-Host "Deployment succeeded."
}
catch {
Write-Host "Deployment failed."
$ex = $_.Exception
Write-Host $_.Exception.Message
Write-Host "Deployment failed."
if ($deleteOnErrorPrompt) {
$reply = Read-Host -Prompt "Delete resource group? [y/n]"
if ($reply -match "[yY]") {
try {
Write-Host "Remove resource group "$script:resourceGroupName
Remove-AzureRmResourceGroup -Name $script:resourceGroupName -Force
}
catch {
Write-Host $_.Exception.Message
}
try {
Write-Host "Delete AD App "$aadConfig.ServiceDisplayName
Remove-AzureADApplication -ObjectId $aadConfig.ServiceObjectId
Write-Host "Delete AD App "$aadConfig.ClientDisplayName
Remove-AzureADApplication -ObjectId $aadConfig.ClientObjectId
Write-Host "Delete AD App "$aadConfig.ModuleDisplayName
Remove-AzureADApplication -ObjectId $aadConfig.ModuleObjectId
}
catch {
Write-Host $_.Exception.Message
}
}
}
throw $ex
}
# publishing slot
$slotParameters = @{ Slot = "Production" }
$deploydir = pwd
# build and publish the service webapp
Write-Host 'Publish service'
$publishFolder = Join-Path -Path $deploydir -ChildPath "\service"
if(Test-path $publishFolder) {Remove-Item -Recurse -Force $publishFolder}
dotnet publish -c Debug -o $publishFolder ..\src\Microsoft.Azure.IIoT.OpcUa.Services.Vault.csproj
ZipDeploy $resourceGroupName $webServiceName $publishFolder $slotParameters
# build and publish the client webapp
Write-Host 'Publish application'
$publishFolder = Join-Path -Path $deploydir -ChildPath "\app"
if(Test-path $publishFolder) {Remove-Item -Recurse -Force $publishFolder}
dotnet publish -c Debug -o $publishFolder ..\app\Microsoft.Azure.IIoT.OpcUa.Services.Vault.App.csproj
ZipDeploy $resourceGroupName $webAppName $publishFolder $slotParameters
# build configuration options for module
$moduleConfiguration = '--vault="'+$serviceUrls[1]+'"'
$moduleConfiguration += ' --resource="'+$($aadConfig.ServiceId)+'"'
$moduleConfiguration += ' --clientid="'+$($aadConfig.ModuleId)+'"'
$moduleConfiguration += ' --secret="'+$($aadConfig.ModuleSecret)+'"'
$moduleConfiguration += ' --tenantid="'+$($aadConfig.TenantId)+'"'
# save config for user, e.g. for VS debugging of the module
$moduleConfigPath = Join-Path -Path $deploydir -ChildPath "$($resourceGroupName).module.config"
Write-Output $moduleConfiguration | Out-File -FilePath $moduleConfigPath -Encoding ascii
# output information
Write-Host "GDS module configuration:"
Write-Host "--vault="$serviceUrls[1]
Write-Host "--resource="$aadConfig.ServiceId
Write-Host "--clientid="$aadConfig.ModuleId
Write-Host "--secret="$aadConfig.ModuleSecret
Write-Host "--tenantid="$aadConfig.TenantId
# prepare the GDS module docker image
cd ..\module\docker\linux
.\dockerbuild.bat
cd $deploydir
# create batch file for user to start GDS docker container
$dockerrun = 'docker run -it -p 58850-58852:58850-58852 -e 58850-58852 -h %COMPUTERNAME% -v "/c/GDS:/root/.local/share/Microsoft/GDS" edgeopcvault:latest '
$dockerrun += $moduleConfiguration
$dockerrunfilename = ".\"+$resourceGroupName+"-dockergds.cmd"
Write-Output $dockerrun | Out-File -FilePath $dockerrunfilename -Encoding ascii
# create batch file for user to start GDS as dotnet app
$apprun = "cd ..\module `r`n"
$apprun += 'dotnet run --project ..\module\Microsoft.Azure.IIoT.OpcUa.Modules.Vault.csproj '
$apprun += $moduleConfiguration
$apprunfilename = ".\"+$resourceGroupName+"-gds.cmd"
Write-Output $apprun | Out-File -FilePath $apprunfilename -Encoding ascii
# deployment info
Write-Host
Write-Host "To access the web client go to:"
Write-Host $serviceUrls[0]
Write-Host
Write-Host "To access the web service go to:"
Write-Host $serviceUrls[1]
Write-Host
Write-Host "To start the local docker GDS server:"
Write-Host $dockerrunfilename
Write-Host
Write-Host "To start the local dotnet GDS server:"
Write-Host $apprunfilename
Write-Host

Двоичные данные
docs/ApproveReject.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Двоичные данные
docs/GenerateNewKeyPair.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 53 KiB

Двоичные данные
docs/RequestNewCertificate.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 32 KiB

Двоичные данные
docs/UAReferenceServerRegistration.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 50 KiB

Двоичные данные
docs/ViewKeyPair.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 44 KiB

Просмотреть файл

@ -0,0 +1,169 @@
# Build and Deploy the Azure Industrial IoT OPC UA Certificate Management Service and dependencies
This article explains how to deploy the OPC UA Certificate Management Service in Azure.
## Prerequisites
### Install required software
Currently the build and deploy operation is limited to Windows.
The samples are all written for .NetStandard, which is needed to build the service and samples for deployment.
All the tools you need for .Net Standard come with the .Net Core tools. See [here](https://docs.microsoft.com/en-us/dotnet/articles/core/getting-started) for what you need.
1. [Install .NET Core 2.1+][dotnet-install].
2. [Install Docker][docker-url].
4. Install the [Azure Command Line tools for PowerShell][powershell-install].
5. Sign up for an [Azure Subscription][azure-free].
### Clone the repository
If you have not done so yet, clone this Github repository. Open a command prompt or terminal and run:
```bash
git clone https://github.com/Azure/azure-iiot-opc-vault-service && cd azure-iiot-opc-vault-service
```
or clone the repo directly in Visual Studio 2017.
### Build and Deploy the Azure service on Windows
A Powershell script provides an easy way to deploy the OPC UA Vault service and the application.<br>
1. Open a Powershell window at the repo root.
3. Go to the deploy folder `cd deploy`
5. Start the deployment with `.\deploy.ps1` for interactive installation<br>
or enter a full command line:
`.\deploy.ps1 -subscriptionName "MySubscriptionName" -resourceGroupLocation "East US" -tenantId "myTenantId" -resourceGroupName "myResourceGroup"`
6. Follow the instructions in the script to login to your subscription and to provide additional information
9. After a successful build and deploy operation you should see the following message:
```
To access the web client go to:
https://myResourceGroup-app.azurewebsites.net
To access the web service go to:
https://myResourceGroup-service.azurewebsites.net
To start the local docker GDS server:
.\myResourceGroup-dockergds.cmd
To start the local dotnet GDS server:
.\myResourceGroup-gds.cmd
```
In case you run into issues please follow the steps [below](#Troubleshooting-deployment-failures).
6. Give the web app and the web service a few minutes to start up for the first time.
10. Open your favorite browser and open the application page: `https://myResourceGroup-app.azurewebsites.net`
11. To take a look at the Swagger Api open: `https://myResourceGroup-service.azurewebsites.net`
13. To start a local GDS server with dotnet start `.\myResourceGroup-gds.cmd` or with docker start `.\myResourceGroup-dockergds.cmd`.
As a sidenote, it is possible to redeploy a build with exactly the same settings. Be aware that such an operation renews all application secrets and may reset some settings in the AAD application registrations.
### Create the root CA certificate
1. Open your certificate service at `https://myResourceGroup-app.azurewebsites.net` and login.
2. Navigate to the `Certificate Groups` page.
3. There is one `Default` Certificate Group listed. Click on `Edit`.
4. In `Edit Certificate Group Details` you can modify the Subject Name and Lifetime of your CA and application certificates.
5. Enter a valid Subject in the valid, e.g. `CN=My CA Root, O=MyCompany, OU=MyDepartment`.
6. Click on the `Save` button.
1. If you hit a 'forbidden' error at this point, the user you are logged in with doesn't have the rights to modify or create a new root cert. By default the user who deployed the service has management and signing roles with the service, other users need to be added to the 'Approver', 'Writer' or 'Administrator' roles as appropriate in the AzureAD application registration.
7. Click on the `Details` button. The `View Certificate Group Details` should display the updated information.
8. Click on the `Renew CA Certificate` button to issue your first root CA certificate. Press `Ok` to proceed.
9. After a few seconds the `Certificate Details` are shown. Press `Issuer` or `Crl` to download the latest CA certificate and CRL for distribution to your OPC UA applications.
10. Now the OPC UA Certificate Management Service is ready to issue certificates for OPC UA applications.
### Register your OPC UA application and create a new key pair and certificate
1. Open your certificate service at `https://myResourceGroup-app.azurewebsites.net` and login.
2. Navigate to the `Register New` page.
1. For an application registration a user needs to have at least the 'Writer' role assigned.
2. The entry form follows naming conventions in the OPC UA world. As an example, in the picture below the settings for the [OPC UA Reference Server](https://github.com/OPCFoundation/UA-.NETStandard/tree/master/SampleApplications/Workshop/Reference) sample in the OPC UA .NetStandard stack is shown:
![UA Reference Server Registration](UAReferenceServerRegistration.png "UA Reference Server Registration")
5. Press the `Register` button to register the application in the certificate service application database. The workflow directly guides the user to the next step to request a signed certificate for the application.
![Request New Certificate](RequestNewCertificate.png "Request New Certificate")
6. Press 'Request new KeyPair and Certificate' to request a new certificate for your application.
![Generate New Key Pair](GenerateNewKeyPair.png "Generate New Key Pair")
7. Fill in the form with a subject, the domain names and choose PEM or PFX with password for the private key. Press the `Generate New Certificate` button to create the certificate request.
![Approve Certificate](ApproveReject.png "Approve Certificate")
8. Approve or Reject the certificate request to start or cancel the actual creation of the key pair and the signing operation. The new key pair is created and stored securely in Azure Key Vault until downloaded by the certificate requester. The resulting certificate with public key is signed by the CA. These operations may take a few seconds to finish.
![View Key Pair](ViewKeyPair.png "View Key Pair")
9. The resulting private key (PFX or PEM) and certificate (DER) can be downloaded from here in the format selected as binary file download. A base64 encoded version is also available, e.g. to copy paste the certificate to a command line or text entry.
10. Once the private key is downloaded and stored securely, it can be deleted from the service with the `Delete Private Key` button. The certificate with public key remains available for future use.
11. Due to the use of a CA signed certificate, the CA cert and CRL should be downloaded here as well.
12. Now it depends on the OPC UA device how to apply the new key pair. Typically, the CA cert and CRL are copied to a `trusted` folder, while the public and private key of the application certificate is applied to a `own` folder in the certificate store. Some devices may already support 'Server Push' for Certificate updates. Please refer to the documentation of your OPC UA device.
## Troubleshooting deployment failures
### Resource group name
Ensure you use a short and simple resource group name. The name is used also to name resources and the service url prefix and as such, it must comply with resource naming requirements.
### Website name already in use
It is possible that the name of the website is already in use. If you run into this error, you need to use a different resource group name.
### Azure Active Directory (AAD) Registration
The deployment script tries to register 3 AAD applications in Azure Active Directory.
Depending on your rights to the selected AAD tenant, this operation might fail. There are 2 options:
1. If you chose a AAD tenant from a list of tenants, restart the script and choose a different one from the list.
2. Alternatively, deploy a private AAD tenant in another subscription, restart the script and select to use it.
## Deployment script options
The script takes the following parameters:
```
-resourceGroupName
```
Can be the name of an existing or a new resource group.
```
-subscriptionId
```
Optional, the subscription id where resources will be deployed.
```
-subscriptionName
```
Or alternatively the subscription name.
```
-resourceGroupLocation
```
Optional, a resource group location. If specified, will try to create a new resource group in this location.
```
-tenantId
```
AAD tenant to use.
[azure-free]:https://azure.microsoft.com/en-us/free/
[powershell-install]:https://azure.microsoft.com/en-us/downloads/#PowerShell
[docker-url]: https://www.docker.com/
[dotnet-install]: https://www.microsoft.com/net/learn/get-started

Просмотреть файл

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
>
<ApplicationName>Azure Industrial IoT OPC UA Global Discovery Server</ApplicationName>
<ApplicationName>Azure IoT OPC UA Global Discovery Server</ApplicationName>
<ApplicationUri>urn:localhost:azure.microsoft.com:iiot:GlobalDiscoveryServer</ApplicationUri>
<ProductUri>http://azure.microsoft.com/iiot/UA/GlobalDiscoveryServer</ProductUri>
<ApplicationType>Server_0</ApplicationType>
@ -13,7 +13,7 @@
<ApplicationCertificate>
<StoreType>X509Store</StoreType>
<StorePath>CurrentUser\My</StorePath>
<SubjectName>CN=OPC UA Global Discovery Server, O=Microsoft Corp., OU=Azure Industrial IoT</SubjectName>
<SubjectName>CN=OPC UA Global Discovery Server, O=Microsoft Corp., OU=Azure IoT</SubjectName>
<!-- <Thumbprint></Thumbprint> -->
</ApplicationCertificate>
@ -188,4 +188,4 @@
<TraceMasks>519</TraceMasks>
</TraceConfiguration>
</ApplicationConfiguration>
</ApplicationConfiguration>

Просмотреть файл

@ -1,4 +1,4 @@
// ------------------------------------------------------------
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
@ -86,8 +86,8 @@ namespace Opc.Ua.Gds.Server
{ "r|resource=", "OpcVault Resource Id", r => opcVaultOptions.ResourceId = r },
{ "c|clientid=", "AD Client Id", c => azureADOptions.ClientId = c },
{ "s|secret=", "AD Client Secret", s => azureADOptions.ClientSecret = s },
{ "a|authority", "Authority", a => azureADOptions.Authority = a },
{ "t|tenantid", "Tenant Id", t => azureADOptions.TenantId = t },
{ "a|authority=", "Authority", a => azureADOptions.Authority = a },
{ "t|tenantid=", "Tenant Id", t => azureADOptions.TenantId = t },
{ "h|help", "show this message and exit", h => showHelp = h != null },
};

Просмотреть файл

@ -1,14 +0,0 @@
[
{
"Id": "Default",
"CertificateType": "RsaSha256ApplicationCertificateType",
"SubjectName": "CN=Azure Industrial IoT GDS CA, O=Microsoft Corp.",
"BaseStorePath": "/default",
"DefaultCertificateLifetime": 12,
"DefaultCertificateKeySize": 2048,
"DefaultCertificateHashSize": 256,
"CACertificateLifetime": 60,
"CACertificateKeySize": 2048,
"CACertificateHashSize": 256
}
]

Просмотреть файл

@ -1,4 +1,4 @@
FROM microsoft/dotnet:2.0.0-runtime
FROM microsoft/dotnet:2.2-runtime
COPY ./publish /publish
WORKDIR /publish

Просмотреть файл

@ -1,3 +1,4 @@
dotnet build Microsoft.Azure.IIoT.OpcUa.Modules.Vault.csproj
dotnet publish Microsoft.Azure.IIoT.OpcUa.Modules.Vault.csproj -o ./publish
docker build -t edgegds .
dotnet build ..\..\Microsoft.Azure.IIoT.OpcUa.Modules.Vault.csproj
dotnet publish ..\..\Microsoft.Azure.IIoT.OpcUa.Modules.Vault.csproj -o ./docker/linux/publish
docker build -t edgeopcvault .

Просмотреть файл

@ -1,3 +1,4 @@
rem start docker with mapped logs
rem push image: docker push mregen/edgegds:latest
docker run -it -p 58850-58852:58850-58852 -e 58850-58852 -h edgegds -v "/c/GDS:/root/.local/share/Microsoft/GDS" edgegds:latest --g http://opcvault.azurewebsites.net/"
rem push image: docker push mregen/edgeopcvault:latest
docker run -it -p 58850-58852:58850-58852 -e 58850-58852 -h %COMPUTERNAME% -v "/c/GDS:/root/.local/share/Microsoft/GDS" edgeopcvault:latest --vault="https://vault012-service.azurewebsites.net" --resource="46f44d87-87a2-4d91-ad34-f0ed5d6031ed" --clientid="f5a38dd7-4282-49eb-b1f3-d50b72462588" --secret="ydZ0rxTzsDrik09c4sFRKmF0jgNO0yAB+93vcdRLCs4=" --tenantid="660722d6-c658-431c-8b2e-a157f3134da5"

Просмотреть файл

@ -110,10 +110,16 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
{
var accessToken = request.Headers["Authorization"];
var token = accessToken.First().Remove(0, "Bearer ".Length);
var authority = String.IsNullOrEmpty(_clientConfig.InstanceUrl) ? _kAuthority : _clientConfig.InstanceUrl;
if (!authority.EndsWith("/"))
{
authority += "/";
}
authority += _clientConfig.TenantId;
var serviceClientCredentials =
new KeyVaultCredentials(
token,
(String.IsNullOrEmpty(_clientConfig.InstanceUrl) ? _kAuthority : _clientConfig.InstanceUrl) + _clientConfig.TenantId,
authority,
_servicesConfig.KeyVaultResourceId,
_clientConfig.AppId,
_clientConfig.AppSecret);

Просмотреть файл

@ -43,10 +43,12 @@
<Folder Include="Properties" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.1.3" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.2.1" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.2" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.5.1" />
</ItemGroup>
<Choose>
<When Condition="Exists('..\..\UA-.NETStandard') and '$(Configuration)'=='Develop'">
@ -76,8 +78,7 @@
</When>
<Otherwise>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IIoT.Services" Version="1.0.*" />
<PackageReference Include="Microsoft.Azure.IIoT.Abstractions" Version="1.0.*" />
<PackageReference Include="Microsoft.Azure.IIoT.Services" Version="1.0.0" />
</ItemGroup>
</Otherwise>
</Choose>
@ -96,4 +97,7 @@
<ItemGroup>
<Content Update="appsettings.*.json" CopyToPublishDirectory="Never" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services" />
</ItemGroup>
</Project>

Просмотреть файл

@ -41,6 +41,7 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
Console.WriteLine($"[{Uptime.ProcessId}] Starting web service, process ID: " + Uptime.ProcessId);
var host = new WebHostBuilder()
.UseApplicationInsights()
.UseConfiguration(configRoot)
.UseKestrel(options => { options.AddServerHeader = false; })
.UseIISIntegration()

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
using Microsoft.Azure.KeyVault.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault.Runtime
{
public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager
{
private readonly string _prefix;
public PrefixKeyVaultSecretManager(string prefix)
{
_prefix = $"{prefix}-";
}
public bool Load(SecretItem secret)
{
// Load a vault secret when its secret name starts with the
// prefix. Other secrets won't be loaded.
return secret.Identifier.Name.StartsWith(_prefix);
}
public string GetKey(SecretBundle secret)
{
// Remove the prefix from the secret name and replace two
// dashes in any name with the KeyDelimiter, which is the
// delimiter used in configuration (usually a colon). Azure
// Key Vault doesn't allow a colon in secret names.
return secret.SecretIdentifier.Name
.Substring(_prefix.Length)
.Replace("--", ConfigurationPath.KeyDelimiter);
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@
namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
{
using System;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
@ -21,7 +22,6 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Swagger;
using System;
using CorsSetup = IIoT.Services.Cors.CorsSetup;
using ILogger = Microsoft.Azure.IIoT.Diagnostics.ILogger;
@ -54,13 +54,31 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
{
Environment = env;
IConfigurationRoot config = new ConfigurationBuilder()
var configBuilder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true)
.AddEnvironmentVariables()
.Build();
.AddEnvironmentVariables();
IConfigurationRoot config;
try
{
var builtConfig = configBuilder.Build();
var keyVault = builtConfig["KeyVault"];
if (!String.IsNullOrWhiteSpace(keyVault))
{
configBuilder.AddAzureKeyVault(
keyVault,
builtConfig["Auth:AppId"],
builtConfig["Auth:AppSecret"],
new PrefixKeyVaultSecretManager("Service")
);
}
}
catch
{
}
config = configBuilder.Build();
Config = new Config(config);
}
@ -100,6 +118,8 @@ namespace Microsoft.Azure.IIoT.OpcUa.Services.Vault
options.SerializerSettings.MaxDepth = 10;
});
services.AddApplicationInsightsTelemetry();
services.AddSwagger(Config, new Info
{
Title = ServiceInfo.NAME,

Просмотреть файл

@ -1,117 +1,121 @@
{
"OpcVault": {
//
// KeyVault service uri from Azure portal
//
// "KeyVaultResourceId": "https://vault.azure.net"
// "KeyVaultBaseUrl": "",
//
// CosmosDB service uri and token from Azure portal
//
// "CosmosDBEndpoint": "",
// "CosmosDBToken": ""
},
//
// Can be used when running services on multiple hostnames and/or ports
// e.g. "*" or "{ 'origins': ['*'], 'methods': ['*'], 'headers': ['*'] }"
// to allow everything. Leave it empty to disable CORS.
//
"CorsWhitelist": "*",
//
// Auth configuration
//
"Auth": {
//
// This can be changed to false, for example during development, to allow
// invalid/missing authorizations.
//
"Required": true,
{
"OpcVault": {
//
// KeyVault service uri from Azure portal
//
// "KeyVaultResourceId": "https://vault.azure.net"
// "KeyVaultBaseUrl": "",
//
// CosmosDB service uri and token from Azure portal
//
// "CosmosDBEndpoint": "",
// "CosmosDBToken": ""
},
//
// Identifies the security token service (STS) that constructs and
// returns the token. In the tokens that Azure AD returns, the
// issuer is sts.windows.net. The GUID in the Issuer claim value is
// the tenant ID of the Azure AD directory. The tenant ID is an
// immutable and reliable identifier of the directory. Used to verify
// that tokens are issued by Azure AD.
// Can be used when running services on multiple hostnames and/or ports
// e.g. "*" or "{ 'origins': ['*'], 'methods': ['*'], 'headers': ['*'] }"
// to allow everything. Leave it empty to disable CORS.
//
// When using Azure Active Directory, the format of the Issuer is:
// https://sts.windows.net/<tenant_Id>/
// example: issuer:
// https://sts.windows.net/fa01ade2-2365-4dd1-a084-a6ef027090fc/
//
// "TrustedIssuer": "",
"CorsWhitelist": "*",
//
// The authority to use to issue tokens, by default this is
// https://login.microsoftonline.com/<tenant_Id>/.
// Auth configuration
//
// "Authority": "",
"Auth": {
//
// This can be changed to false, for example during development, to allow
// invalid/missing authorizations.
//
"Required": true,
//
// The audience for tokens, typically this is the application id.
//
// "Audience": "",
//
// Identifies the security token service (STS) that constructs and
// returns the token. In the tokens that Azure AD returns, the
// issuer is sts.windows.net. The GUID in the Issuer claim value is
// the tenant ID of the Azure AD directory. The tenant ID is an
// immutable and reliable identifier of the directory. Used to verify
// that tokens are issued by Azure AD.
//
// When using Azure Active Directory, the format of the Issuer is:
// https://sts.windows.net/<tenant_Id>/
// example: issuer:
// https://sts.windows.net/fa01ade2-2365-4dd1-a084-a6ef027090fc/
//
// "TrustedIssuer": "",
//
// The optional tenant id. The tenant ID is an immutable and reliable
// identifier of the directory.
//
// "TenantId": "",
//
// The authority to use to issue tokens, by default this is
// https://login.microsoftonline.com/<tenant_Id>/.
//
// "Authority": "",
//
// The application id
//
// "AppId": "",
//
// The audience for tokens, typically this is the application id.
//
// "Audience": "",
//
// The application secret for on behalf of authentication
//
// "AppSecret": "",
//
// The optional tenant id. The tenant ID is an immutable and reliable
// identifier of the directory.
//
// "TenantId": "",
//
// When validating the token expiration, allows some clock skew
// Default: 2 minutes
//
"AllowedClockSkewSeconds": 300
},
//
// Swagger
//
"Swagger": {
// Swagger needs an app registration.
"Enabled": true
//
// The application id as registered in AAS. Retrieve from portal
// as a guid, e.g. fa01ade2-2365-4dd1-a084-a6ef027090fc
// The reply url of the registration is:"http://<hostname>/oauth2-redirect.html"
//
// The application id
//
// "AppId": "",
//
// "AppId": "",
//
// The application secret for on behalf of authentication
//
// "AppSecret": "",
// the app secret of the swagger app registration
// "AppSecret": ""
},
//
// Logging
//
"Logging": {
//
// When validating the token expiration, allows some clock skew
// Default: 2 minutes
//
"AllowedClockSkewSeconds": 300
},
//
// For more information about ASP.NET logging see
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging
// This configuration block is used only to capture internal logs generated
// by ASP.NET
// Swagger
//
"IncludeScopes": true,
"Swagger": {
// Swagger needs an app registration.
"Enabled": true
//
// The application id as registered in AAS. Retrieve from portal
// as a guid, e.g. fa01ade2-2365-4dd1-a084-a6ef027090fc
// The reply url of the registration is:"http://<hostname>/oauth2-redirect.html"
//
// "AppId": "",
// the app secret of the swagger app registration
// "AppSecret": ""
},
//
// ASP.NET log levels: Trace, Debug, Information, Warning, Error, Critical
// KeyVault for service configuration
//
"LogLevel": {
"Default": "Warning",
"System": "Warning",
"Microsoft": "Warning"
// "KeyVault": null,
//
// Logging
//
"Logging": {
//
// For more information about ASP.NET logging see
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging
// This configuration block is used only to capture internal logs generated
// by ASP.NET
//
"IncludeScopes": true,
//
// ASP.NET log levels: Trace, Debug, Information, Warning, Error, Critical
//
"LogLevel": {
"Default": "Warning",
"System": "Warning",
"Microsoft": "Warning"
}
}
}
}