diff --git a/deployment/Deploy.ps1 b/deployment/Deploy.ps1 index 4485745b..aac33a40 100644 --- a/deployment/Deploy.ps1 +++ b/deployment/Deploy.ps1 @@ -287,10 +287,45 @@ if (!($ADApplicationID)) { sleep 5 #this is to give time to AAD to register # create service principal az ad sp create --id $ADApplicationID - $ADApplicationSecret = az ad app credential reset --id $ADObjectID --append --display-name 'SaaSAPI' --years 2 --query password --only-show-errors --output tsv - + # $ADApplicationSecret = az ad app credential reset --id $ADObjectID --append --display-name 'SaaSAPI' --years 2 --query password --only-show-errors --output tsv + Write-Host " 🔵 FulfilmentAPI App Registration created." Write-Host " ➡️ Application ID:" $ADApplicationID + + Write-Host "Creating PEM certificate" + $certKeyFile = "cert.key" + $certPemFile = "cert.pem" + $certSubject = "/CN="+$WebAppNamePrefix + $certPfxFile = "cert.pfx" + openssl req -x509 -nodes -days 730 -newkey rsa:2048 -keyout $certKeyFile -out $certPemFile -subj $certSubject + + Write-Host "Converting PEM and KEY to PFX" + $random = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $bytes = New-Object byte[] 16 + $random.GetBytes($bytes) + $certPassword = [Convert]::ToBase64String($bytes) + openssl pkcs12 -export -out $certPfxFile -inkey $certKeyFile -in $certPemFile -passout pass:$certPassword + + Write-Host "Uploading certificate to Azure App Registration..." + + if (Test-Path $certPemFile) { + # Upload the certificate to the Azure App Registration + az ad app credential reset --id $ADApplicationID --cert @$certPemFile --append + Write-Host "Certificate successfully created and uploaded to the app registration." + } else { + Write-Host "Certificate creation failed. The certificate file does not exist." + } + + $currentDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path + # $CertPathPfx = Join-Path -Path $currentDirectory -ChildPath "cert.pfx" + # $policy = New-AzKeyVaultCertificatePolicy -IssuerName "Self" -SubjectName "CN=$WebAppNamePrefix" -SecretContentType 'application/x-pkcs12' -ValidityInMonths (24) + # $secureCertPassword = ConvertTo-SecureString -String $certPassword -AsPlainText -Force + + #Required to save pfx in keyvault + + $pfxPath = Join-Path -Path $currentDirectory -ChildPath "cert.pfx" + $pfxBytes = [System.IO.File]::ReadAllBytes($pfxPath); + $base64Value = [System.Convert]::ToBase64String($pfxBytes); } catch [System.Net.WebException],[System.IO.IOException] { Write-Host "🚨🚨 $PSItem.Exception" @@ -493,11 +528,13 @@ $KvSubnetName="kv" $DefaultSubnetName="default" #keep the space at the end of the string - bug in az cli running on windows powershell truncates last char https://github.com/Azure/azure-cli/issues/10066 -$ADApplicationSecretKeyVault="@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=ADApplicationSecret) " +# $ADApplicationSecretKeyVault="@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=ADApplicationSecret) " $DefaultConnectionKeyVault="@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=DefaultConnection) " $ServerUri = $SQLServerName+".database.windows.net" $ServerUriPrivate = $SQLServerName+".privatelink.database.windows.net" $Connection="Server=tcp:"+$ServerUriPrivate+";Database="+$SQLDatabaseName+";TrustServerCertificate=True;Authentication=Active Directory Managed Identity;" +$FulfillmentAppCertificate = "@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=pfx-cert) " +$FulfillmentAppCertificatePassword = "@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=pfx-pwd) " Write-host " 🔵 Resource Group" Write-host " ➡️ Create Resource Group" @@ -531,11 +568,10 @@ Write-host " 🔵 KeyVault" Write-host " ➡️ Create KeyVault" az keyvault create --name $KeyVault --resource-group $ResourceGroupForDeployment --enable-rbac-authorization false --output $azCliOutput Write-host " ➡️ Add Secrets" -az keyvault secret set --vault-name $KeyVault --name ADApplicationSecret --value="$ADApplicationSecret" --output $azCliOutput +# az keyvault secret set --vault-name $KeyVault --name ADApplicationSecret --value="$ADApplicationSecret" --output $azCliOutput az keyvault secret set --vault-name $KeyVault --name DefaultConnection --value $Connection --output $azCliOutput -Write-host " ➡️ Update Firewall" -az keyvault update --name $KeyVault --resource-group $ResourceGroupForDeployment --default-action Deny --output $azCliOutput -az keyvault network-rule add --name $KeyVault --resource-group $ResourceGroupForDeployment --vnet-name $VnetName --subnet $WebSubnetName --output $azCliOutput +az keyvault secret set --vault-name $KeyVault --name "pfx-cert" --value $base64Value --output $azCliOutput +az keyvault secret set --vault-name $KeyVault --name "pfx-pwd" --value $certPassword --output $azCliOutput Write-host " 🔵 App Service Plan" Write-host " ➡️ Create App Service Plan" @@ -550,7 +586,7 @@ Write-host " ➡️ Setup access to KeyVault" az keyvault set-policy --name $KeyVault --object-id $WebAppNameAdminId --secret-permissions get list --key-permissions get list --resource-group $ResourceGroupForDeployment --output $azCliOutput Write-host " ➡️ Set Configuration" az webapp config connection-string set -g $ResourceGroupForDeployment -n $WebAppNameAdmin -t SQLAzure --output $azCliOutput --settings DefaultConnection=$DefaultConnectionKeyVault -az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNameAdmin --output $azCliOutput --settings KnownUsers=$PublisherAdminUsers SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__ClientSecret=$ADApplicationSecretKeyVault SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADApplicationIDAdmin SaaSApiConfiguration__IsAdminPortalMultiTenant=$IsAdminPortalMultiTenant SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash +az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNameAdmin --output $azCliOutput --settings KnownUsers=$PublisherAdminUsers SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__KeyVault=$KeyVault SaaSApiConfiguration__ClientCertificate=$FulfillmentAppCertificate SaaSApiConfiguration__ClientCertificatePassword=$FulfillmentAppCertificatePassword SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADApplicationIDAdmin SaaSApiConfiguration__IsAdminPortalMultiTenant=$IsAdminPortalMultiTenant SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash az webapp config set -g $ResourceGroupForDeployment -n $WebAppNameAdmin --always-on true --output $azCliOutput Write-host " 🔵 Customer Portal WebApp" @@ -562,7 +598,7 @@ Write-host " ➡️ Setup access to KeyVault" az keyvault set-policy --name $KeyVault --object-id $WebAppNamePortalId --secret-permissions get list --key-permissions get list --resource-group $ResourceGroupForDeployment --output $azCliOutput Write-host " ➡️ Set Configuration" az webapp config connection-string set -g $ResourceGroupForDeployment -n $WebAppNamePortal -t SQLAzure --output $azCliOutput --settings DefaultConnection=$DefaultConnectionKeyVault -az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNamePortal --output $azCliOutput --settings SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__ClientSecret=$ADApplicationSecretKeyVault SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADMTApplicationIDPortal SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash +az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNamePortal --output $azCliOutput --settings SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__KeyVault=$KeyVault SaaSApiConfiguration__ClientCertificate=$FulfillmentAppCertificate SaaSApiConfiguration__ClientCertificatePassword=$FulfillmentAppCertificatePassword SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADMTApplicationIDPortal SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash az webapp config set -g $ResourceGroupForDeployment -n $WebAppNamePortal --always-on true --output $azCliOutput #endregion @@ -572,7 +608,7 @@ Write-host "📜 Deploy Code" Write-host " 🔵 Deploy Database" Write-host " ➡️ Generate SQL schema/data script" -Set-Content -Path ../src/AdminSite/appsettings.Development.json -value "{`"ConnectionStrings`": {`"DefaultConnection`":`"$Connection`"}}" +Set-Content -Path ../src/AdminSite/appsettings.Development.json -value "{`"SaaSApiConfiguration`":{`"KeyVault`": `"$KeyVault`"}, `"ConnectionStrings`": {`"DefaultConnection`":`"$Connection`"}}" dotnet-ef migrations script --output script.sql --idempotent --context SaaSKitContext --project ../src/DataAccess/DataAccess.csproj --startup-project ../src/AdminSite/AdminSite.csproj Write-host " ➡️ Execute SQL schema/data script" $dbaccesstoken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token @@ -593,6 +629,10 @@ az webapp vnet-integration add --resource-group $ResourceGroupForDeployment --na az webapp vnet-integration add --resource-group $ResourceGroupForDeployment --name $WebAppNameAdmin --vnet $VnetName --subnet $WebSubnetName --output $azCliOutput az sql server vnet-rule create --name $WebAppNamePrefix-vnet --resource-group $ResourceGroupForDeployment --server $SQLServerName --vnet-name $VnetName --subnet $WebSubnetName --output $azCliOutput +Write-host " ➡️ Update Firewall for KeyVault" +az keyvault update --name $KeyVault --resource-group $ResourceGroupForDeployment --default-action Deny --output $azCliOutput +az keyvault network-rule add --name $KeyVault --resource-group $ResourceGroupForDeployment --vnet-name $VnetName --subnet $WebSubnetName --output $azCliOutput + Write-host " 🔵 Clean up" Remove-Item -Path ../src/AdminSite/appsettings.Development.json Remove-Item -Path script.sql diff --git a/src/AdminSite/Startup.cs b/src/AdminSite/Startup.cs index b9e5cbf5..71dc294d 100644 --- a/src/AdminSite/Startup.cs +++ b/src/AdminSite/Startup.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using Azure.Identity; using Marketplace.SaaS.Accelerator.AdminSite.Controllers; using Marketplace.SaaS.Accelerator.DataAccess.Context; @@ -75,6 +76,7 @@ public void ConfigureServices(IServiceCollection services) ClientSecret = this.Configuration["SaaSApiConfiguration:ClientSecret"] ?? String.Empty, FulFillmentAPIBaseURL = this.Configuration["SaaSApiConfiguration:FulFillmentAPIBaseURL"], MTClientId = this.Configuration["SaaSApiConfiguration:MTClientId"] ?? Guid.Empty.ToString(), + KeyVault = this.Configuration["SaaSApiConfiguration:KeyVault"] ?? String.Empty, FulFillmentAPIVersion = this.Configuration["SaaSApiConfiguration:FulFillmentAPIVersion"], GrantType = this.Configuration["SaaSApiConfiguration:GrantType"], Resource = this.Configuration["SaaSApiConfiguration:Resource"], @@ -87,7 +89,17 @@ public void ConfigureServices(IServiceCollection services) { KnownUsers = this.Configuration["KnownUsers"], }; - var creds = new ClientSecretCredential(config.TenantId.ToString(), config.ClientId.ToString(), config.ClientSecret); + + string keyVaultUrl = $"https://{config.KeyVault}.vault.azure.net/"; + string certificateName = "pfx-cert"; + string certificatePassword = "pfx-pwd"; + + var certHelper = new CertificateHelper(keyVaultUrl, certificateName, certificatePassword); + + // Use the synchronous method to get the certificate + X509Certificate2 certificate = certHelper.GetCertificate(); + + var creds = new ClientCertificateCredential(config.TenantId.ToString(), config.ClientId.ToString(), certificate); var boolMultiTenant = config.IsAdminPortalMultiTenant?.ToLower().Trim() ?? "false"; diff --git a/src/AdminSite/appsettings.Development.json.rename b/src/AdminSite/appsettings.Development.json.rename index 66b3bbab..88a3f74c 100644 --- a/src/AdminSite/appsettings.Development.json.rename +++ b/src/AdminSite/appsettings.Development.json.rename @@ -15,6 +15,7 @@ // "clientsecret": "", // "mtclientid": "", // "isadminportalmultitenant" : "false", + // "keyvault": "", // "resource": "20e940b3-4c77-4b0b-9a53-9e16a1b010a7", // "fulfillmentapibaseurl": "https://marketplaceapi.microsoft.com/api", // "signedoutredirecturi": "", diff --git a/src/AdminSite/appsettings.json b/src/AdminSite/appsettings.json index 66b3bbab..88a3f74c 100644 --- a/src/AdminSite/appsettings.json +++ b/src/AdminSite/appsettings.json @@ -15,6 +15,7 @@ // "clientsecret": "", // "mtclientid": "", // "isadminportalmultitenant" : "false", + // "keyvault": "", // "resource": "20e940b3-4c77-4b0b-9a53-9e16a1b010a7", // "fulfillmentapibaseurl": "https://marketplaceapi.microsoft.com/api", // "signedoutredirecturi": "", diff --git a/src/CustomerSite/Startup.cs b/src/CustomerSite/Startup.cs index 815ea57d..f56e6db8 100644 --- a/src/CustomerSite/Startup.cs +++ b/src/CustomerSite/Startup.cs @@ -29,6 +29,8 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Security.Cryptography.X509Certificates; + namespace Marketplace.SaaS.Accelerator.CustomerSite; @@ -70,6 +72,7 @@ public void ConfigureServices(IServiceCollection services) ClientId = this.Configuration["SaaSApiConfiguration:ClientId"], ClientSecret = this.Configuration["SaaSApiConfiguration:ClientSecret"], MTClientId = this.Configuration["SaaSApiConfiguration:MTClientId"], + KeyVault = this.Configuration["SaaSApiConfiguration:KeyVault"], FulFillmentAPIBaseURL = this.Configuration["SaaSApiConfiguration:FulFillmentAPIBaseURL"], FulFillmentAPIVersion = this.Configuration["SaaSApiConfiguration:FulFillmentAPIVersion"], GrantType = this.Configuration["SaaSApiConfiguration:GrantType"], @@ -79,7 +82,17 @@ public void ConfigureServices(IServiceCollection services) TenantId = this.Configuration["SaaSApiConfiguration:TenantId"], Environment = this.Configuration["SaaSApiConfiguration:Environment"] }; - var creds = new ClientSecretCredential(config.TenantId.ToString(), config.ClientId.ToString(), config.ClientSecret); + + string keyVaultUrl = $"https://{config.KeyVault}.vault.azure.net/"; + + string certificateName = "pfx-cert"; + string certificatePassword = "pfx-pwd"; + + var certHelper = new CertificateHelper(keyVaultUrl, certificateName, certificatePassword); + + X509Certificate2 certificate = certHelper.GetCertificate(); + + var creds = new ClientCertificateCredential(config.TenantId.ToString(), config.ClientId.ToString(), certificate); services .AddAuthentication(options => diff --git a/src/CustomerSite/appsettings.Development.json.rename b/src/CustomerSite/appsettings.Development.json.rename index ecba701f..fa926528 100644 --- a/src/CustomerSite/appsettings.Development.json.rename +++ b/src/CustomerSite/appsettings.Development.json.rename @@ -14,6 +14,7 @@ // "clientid": "", // "clientsecret": "", // "mtclientid": "", + // "keyvault": "", // "resource": "20e940b3-4c77-4b0b-9a53-9e16a1b010a7", // "fulfillmentapibaseurl": "https://marketplaceapi.microsoft.com/api", // "signedoutredirecturi": "", diff --git a/src/CustomerSite/appsettings.json b/src/CustomerSite/appsettings.json index 528827e2..6f3b7321 100644 --- a/src/CustomerSite/appsettings.json +++ b/src/CustomerSite/appsettings.json @@ -14,6 +14,7 @@ // "clientsecret": "" // "mtclientid": "", // "tenantid": "", + // "keyvault": "", // "resource": "20e940b3-4c77-4b0b-9a53-9e16a1b010a7", // "fulfillmentapibaseurl": "https://marketplaceapi.microsoft.com/api", // "signedoutredirecturi": "", diff --git a/src/MeteredTriggerJob/Program.cs b/src/MeteredTriggerJob/Program.cs index 893c30a6..34c8e92c 100644 --- a/src/MeteredTriggerJob/Program.cs +++ b/src/MeteredTriggerJob/Program.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using Azure.Identity; using Marketplace.SaaS.Accelerator.DataAccess.Context; using Marketplace.SaaS.Accelerator.DataAccess.Contracts; @@ -38,10 +39,19 @@ static void Main (string[] args) ClientSecret = configuration["SaaSApiConfiguration:ClientSecret"], GrantType = configuration["SaaSApiConfiguration:GrantType"], Resource = configuration["SaaSApiConfiguration:Resource"], - TenantId = configuration["SaaSApiConfiguration:TenantId"] + TenantId = configuration["SaaSApiConfiguration:TenantId"], + KeyVault = configuration["SaaSApiConfiguration:KeyVault"] }; - var creds = new ClientSecretCredential(config.TenantId.ToString(), config.ClientId.ToString(), config.ClientSecret); + string keyVaultUrl = $"https://{config.KeyVault}.vault.azure.net/"; + string certificateName = "pfx-cert"; + string certificatePassword = "pfx-pwd"; + + var certHelper = new CertificateHelper(keyVaultUrl, certificateName, certificatePassword); + + X509Certificate2 certificate = certHelper.GetCertificate(); + + var creds = new ClientCertificateCredential(config.TenantId.ToString(), config.ClientId.ToString(), certificate); var versionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); var services = new ServiceCollection() diff --git a/src/MeteredTriggerJob/appsettings.json b/src/MeteredTriggerJob/appsettings.json index d920bcff..2ca1a98a 100644 --- a/src/MeteredTriggerJob/appsettings.json +++ b/src/MeteredTriggerJob/appsettings.json @@ -5,6 +5,7 @@ "tenantid": "", "clientid": "", "clientsecret": "", + "keyvault": "", "resource": "", "adauthenticationendpoint": "https://login.microsoftonline.com" }, diff --git a/src/Services/Configurations/SaaSApiClientConfiguration.cs b/src/Services/Configurations/SaaSApiClientConfiguration.cs index 2d72f2ba..28473436 100644 --- a/src/Services/Configurations/SaaSApiClientConfiguration.cs +++ b/src/Services/Configurations/SaaSApiClientConfiguration.cs @@ -113,5 +113,12 @@ public class SaaSApiClientConfiguration /// public string IsAdminPortalMultiTenant { get; set; } + /// + /// Gets or sets the keyvault name + /// + /// + /// The Authentication end point. + /// + public string KeyVault { get; set; } } \ No newline at end of file diff --git a/src/Services/Helpers/CertificateHelper.cs b/src/Services/Helpers/CertificateHelper.cs new file mode 100644 index 00000000..01ce7935 --- /dev/null +++ b/src/Services/Helpers/CertificateHelper.cs @@ -0,0 +1,36 @@ +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +public class CertificateHelper +{ + + private readonly SecretClient _secretClient; + private readonly string _certificateName; + private readonly string _certificatePassword; + public CertificateHelper(string keyVaultUrl, string certificateName, string certificatePassword) + { + _secretClient = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential()); + _certificateName = certificateName; + _certificatePassword = certificatePassword; + } + + public async Task GetCertificateAsync() + { + KeyVaultSecret secret = await _secretClient.GetSecretAsync(_certificateName); + byte[] certBytes = Convert.FromBase64String(secret.Value); + + KeyVaultSecret passwordSecret = await _secretClient.GetSecretAsync(_certificatePassword); + string certpassword = passwordSecret.Value; + + return new X509Certificate2(certBytes, certpassword); + + } + + public X509Certificate2 GetCertificate() + { + return GetCertificateAsync().GetAwaiter().GetResult(); + } +} diff --git a/src/Services/Services.csproj b/src/Services/Services.csproj index 5f9d4f5d..7d115436 100644 --- a/src/Services/Services.csproj +++ b/src/Services/Services.csproj @@ -11,6 +11,7 @@ +