diff --git a/.github/workflows/container-image-build-saas-admin-tag copy.yml b/.github/workflows/container-image-build-saas-identity-setup.yml similarity index 100% rename from .github/workflows/container-image-build-saas-admin-tag copy.yml rename to .github/workflows/container-image-build-saas-identity-setup.yml diff --git a/docs/azure-saas-docs/content/en/components/Identity/identity-framework-setup-manual.md b/docs/azure-saas-docs/content/en/components/Identity/identity-framework-setup-manual.md index 14e3082b..f7e958be 100644 --- a/docs/azure-saas-docs/content/en/components/Identity/identity-framework-setup-manual.md +++ b/docs/azure-saas-docs/content/en/components/Identity/identity-framework-setup-manual.md @@ -133,27 +133,9 @@ Note: Collaborators are other developers you wish to help manage your services, --- -### Step 5. Create self-signed certificate +### Step 5. Create a random API Key ->Note: A self-signed certificate is not recommended for production environments. Read more [here](https://azure.github.io/azure-saas/components/identity/permissions-service/#authentication). - -- Create an RSA self-signed certificate to be referenced and uploaded to your Azure AD B2C Tenant -- The .pfx file will be uploaded in a later step to your Azure AD B2C to allow it access to integrate with your Permissions API -- OpenSSL with [Powershell Core (v7.0+)](https://github.com/PowerShell/PowerShell) Example: - -``` -$pswd = Read-Host -Prompt "Please enter a password to encrypt the self signed certificate with" - -# Create a new certificate in .crt format with the subject name as *.azurewebsites.net. You could also provide a specific subject name of your permissions api. -openssl req -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out certificate.crt -keyout certificate.key -subj "/CN=*.azurewebsites.net" - -# Export the .crt and .key to a .pfx format -openssl pkcs12 -export -out selfSignedCertificate.pfx -inkey certificate.key -in certificate.crt -password pass:$pswd - -# Encode pfx to base 64 string to reference in step 13. You may output this when you're ready to copy it with `Write-Host $pfxString` -$pfxBytes = Get-Content "selfSignedCertificate.pfx" -AsByteStream -$pfxString = [System.Convert]::ToBase64String($pfxBytes) -``` +An API Key is required to secure communication with the Permissions API. We suggest using a random string at least 20 characters in length consisting of a good mix of uppercase, lowercase, numbers and special characters. --- @@ -176,11 +158,11 @@ $pfxString = [System.Convert]::ToBase64String($pfxBytes) - Name: TokenEncryptionKeyContainer - Key type: RSA - Key usage: Encryption -- RestApiClientCertificate - - Options: Upload - - Name: *.azurewebsites.net - - File upload: (The .pfx created in step 5) - - Password: (The password created in step 5) +- RestApiKey + - Options: Manual + - Name: RestApiKey + - Secret: (The API Key you created in step 5) + - Key usage: Signature --- @@ -209,7 +191,7 @@ $pfxString = [System.Convert]::ToBase64String($pfxBytes) | azureAdB2cTenantIdSecretValue| (Your Tenant Subscription ID found on your AD B2C dashboard)| | azureAdB2cPermissionsApiClientIdSecretValue| (The Client ID found on your registered Permissions API app page)| | azureAdB2cPermissionsApiClientSecretSecretValue| (The Client Secret Value created in step 8)| -| permissionsApiSslThumbprintSecretValue| (Thumbprint created in step 5)| +| permissionsApiApiKeySecretValue | (API Key created in step 5)| | saasProviderName| (Select a provider name. This name will be used to name the Azure Resources. (e.g. contoso, myapp). Max Length is 8 characters.)| | saasEnvironment| (Select an environment name. (e.g. 'prd', 'stg', 'dev', 'tst'))| | saasInstanceNumber| (Select an instance number. This number will be appended to most Azure Resources created. (e.g. 001, 002, 003))| @@ -241,7 +223,7 @@ $pfxString = [System.Convert]::ToBase64String($pfxBytes) "azureAdB2cPermissionsApiClientSecretSecretValue": { "value": "FooBar" }, - "permissionsApiSslThumbprintSecretValue": { + "permissionsApiApiKeySecretValue": { "value": "FooBar" }, "saasProviderName": { @@ -322,8 +304,7 @@ $pfxString = [System.Convert]::ToBase64String($pfxBytes) | azureAdB2cSignupAdminClientSecretSecretValue| (Secret value created in step 4)| | azureAdB2cTenantIdSecretValue| (Your Tenant Subscription ID found on your AD B2C dashboard)| | permissionsApiHostName| (The FQDN of the Permissions API) | -| permissionsApiCertificateSecretValue| (Certificate encoded to base64 string generated in step 5)| -| permissionsApiCertificatePassphraseSecretValue| (Certificate password generated in step 5)| +| permissionsApiApiKeySecretValue | (API Key generated in step 5)| | saasAppApiScopes| (space delimited string of SaaS App Api scope names (e.g. "test.scope tenant.delete tenant.global.delete tenant.global.read tenant.global.write tenant.read tenant.write")) | | saasProviderName| (created in step 8) | | saasEnvironment| (created in step 8)| @@ -377,18 +358,12 @@ $pfxString = [System.Convert]::ToBase64String($pfxBytes) "azureAdB2cPermissionsApiClientSecretSecretValue": { "value": "FooBar" }, - "permissionsApiSslThumbprintSecretValue": { - "value": "FooBar" - }, "permissionsApiHostName": { "value": "https://asdkdemopermissions.com" }, - "permissionsApiCertificateSecretValue": { + "permissionsApiApiKeySecretValue": { "value": "FooBar" }, - "permissionsApiCertificatePassphraseSecretValue": { - "value": "FooBar1" - }, "saasAppApiScopes": { "value": "test.scope tenant.delete tenant.global.delete tenant.global.read tenant.global.write tenant.read tenant.write" }, diff --git a/docs/azure-saas-docs/content/en/components/Identity/permissions-service.md b/docs/azure-saas-docs/content/en/components/Identity/permissions-service.md index 9dd43bc3..03d63e0e 100644 --- a/docs/azure-saas-docs/content/en/components/Identity/permissions-service.md +++ b/docs/azure-saas-docs/content/en/components/Identity/permissions-service.md @@ -35,10 +35,7 @@ When deployed to Azure, the application is configured to load its secrets from [ ### Authentication -The Permissions Service is secured using [TLS Mutual Authentication](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0#configure-certificate-validation) (Certificate Authentication). The application layer is configured to verify the authenticity of the certificate by comparing the thumbprint of the certificate against a configuration value. For TLS Mutual Authentication to work properly, the web server must also be configured to forward the certificate to the application layer. This is done for you if you deploy the application following the steps in the [Quick Start](../../../quick-start) guide, but if you choose to run the code in an environment you provision, you will need to do [this configuration](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) yourself. - -> **Important!** -> The certificate that gets deployed out of the box by following the Quick Start is a self signed certificate. Self signed certificates are not meant for production use, and it is highly recommended that you replace this certificate with one signed by a Certificate Authority before using this module in production. +The Permissions Service is secured using API Key Authentication. The API Key is set using the `AppSettings:ApiKey` secret and there is middleware on the API that will verify that all incoming requests have an API key that matches on the `x-api-key` header. If you deploy the application following the steps in the [Quick Start](../../../quick-start) guide, an API key is randomly generated for you and uploaded to Azure Key Vault. ### Database @@ -54,10 +51,7 @@ The Permissions Service uses [Swashbuckle](https://www.nuget.org/packages/Swashb ## FAQ and Design Considerations -- Q: Why did we choose to secure the permissions service with certificate authentication over API Keys/JWT Tokens/Another Method? - - A: The communication between Azure AD B2C (our default Identity Provider) and the permissions service must be secured with either [Basic or Certificate Auth](https://docs.microsoft.com/en-us/azure/active-directory-b2c/add-api-connector-token-enrichment?pivots=b2c-custom-policy#configure-the-restful-api-technical-profile) and it is not considered best practice to use Basic authentication in a production environment. - - Permissions are stored in the database in a single table (dbo.Permissions) with 3 pieces of data: Tenant ID, User ID (Email), and PermissionString. All 3 together make the row unique (i.e., you cannot have the same Permission for the same user on the same tenant more than once). Permissions are stored as a string (ex: Admin, User.Read, User.Write) for simplicity and extensibility. You may choose to store these in a separate database table and reference them by ID number if you have a large number of permissions and you want to enforce the types of permissions being assigned. - We have purposefully chosen to flow all CRUD operations on permissions through the [Admin Service](../../../components/admin-service). This is for a number of reasons: - 1. It removes the burden of authorization from the permissions service. All the permissions service needs to worry about is accepting a valid certificate, which only the identity provider and admin service possess. For higher security applications, you may choose to preform more authorization checks before adding permissions + 1. It removes the burden of authorization from the permissions service. All the permissions service needs to worry about is accepting a valid API Key, which only the identity provider and admin service possess. For higher security applications, you may choose to preform more authorization checks before adding permissions 2. It simplifies the architecture. The frontend applications do not need to have any knowledge of the permissions service existing. When a tenant is created, the applications make 1 call to the admin service, and it handles the subsequent call to update the permissions records. diff --git a/docs/azure-saas-docs/content/en/quick-start.md b/docs/azure-saas-docs/content/en/quick-start.md index a5e6b372..79d42b6a 100644 --- a/docs/azure-saas-docs/content/en/quick-start.md +++ b/docs/azure-saas-docs/content/en/quick-start.md @@ -28,8 +28,8 @@ Requirements: Running our pre-built docker image is the recommended way to set up the identity framework, as the image comes pre-installed with all the dependencies necessary for execution. To start, run the following commands: ```bash -docker pull ghcr.io/azure/azure-saas/asdk-identity-setup:v1.0 -docker run -it -v "$(pwd):/data" --name asdk-b2c-deployment ghcr.io/azure/azure-saas/asdk-identity-setup:v1.0 +docker pull ghcr.io/azure/azure-saas/asdk-identity-setup:v1.1 +docker run -it -v "$(pwd):/data" --name asdk-b2c-deployment ghcr.io/azure/azure-saas/asdk-identity-setup:v1.1 ``` This will automatically pull and run the container image and its entrypoint is the [B2C-Create](https://github.com/Azure/azure-saas/blob/main/src/Saas.Identity/Saas.IdentityProvider/scripts/B2C-Create.ps1) powershell script. @@ -86,7 +86,7 @@ Deploying to Azure is easy thanks to our pre-configured ARM (Azure Resource Mana This button will take you to the Azure portal and will pass it the ARM template. You will need the parameters file output from step 1. -1. Click here: [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-saas%2Fv1.0%2Fsrc%2FSaas.IaC%2Fmain.json). +1. Click here: [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-saas%2Fv1.1%2Fsrc%2FSaas.IaC%2Fmain.json). 2. Select "Edit Parameters". 3. Select "Load File" and upload the `parameters.json` file output from the Identity Framework Deployment (step 1 above). Click "Save". 4. From the dropdown, select the subscription and resource group you'd like to deploy the resources to. diff --git a/docs/azure-saas-docs/static/images/identity-framework-manual-step-6-policy-keys.png b/docs/azure-saas-docs/static/images/identity-framework-manual-step-6-policy-keys.png index e16842dc..0886590b 100644 Binary files a/docs/azure-saas-docs/static/images/identity-framework-manual-step-6-policy-keys.png and b/docs/azure-saas-docs/static/images/identity-framework-manual-step-6-policy-keys.png differ diff --git a/src/Saas.Admin/Saas.Admin.Service/Program.cs b/src/Saas.Admin/Saas.Admin.Service/Program.cs index be8d0f9e..bf881844 100644 --- a/src/Saas.Admin/Saas.Admin.Service/Program.cs +++ b/src/Saas.Admin/Saas.Admin.Service/Program.cs @@ -21,14 +21,6 @@ new DefaultAzureCredential(), new CustomPrefixKeyVaultSecretManager("admin")); - // Get certificate from secret imported above and parse it into an X509Certificate - permissionsApiCertificate = new X509Certificate2(Convert.FromBase64String(builder.Configuration["KeyVault:PermissionsApiCert"]), builder.Configuration["KeyVault:PermissionsApiCertPassphrase"]); -} -else -{ - // If running locally, you must first set the certificate as a base 64 encoded string in your .NET secrets manager. - var certString = builder.Configuration["PermissionsApi:LocalCertificate"]; - permissionsApiCertificate = new X509Certificate2(Convert.FromBase64String(certString)); } builder.Services.AddDbContext(options => @@ -108,24 +100,10 @@ builder.Services.AddScoped(); builder.Services.AddHttpClient() - // Configure outgoing HTTP requests to include certificate for permissions API - .ConfigurePrimaryHttpMessageHandler(() => - { - HttpClientHandler handler = new HttpClientHandler(); - handler.ClientCertificates.Add(permissionsApiCertificate); - return handler; - }) .ConfigureHttpClient(options => { options.BaseAddress = new Uri(builder.Configuration["PermissionsApi:BaseUrl"]); - - if (builder.Environment.IsDevelopment()) - { - // The permissions API expects the certificate to be provided to the application layer by the web server after the TLS handshake - // Since this doesn't happen locally, we need to do it ourselves - - options.DefaultRequestHeaders.Add("X-ARR-ClientCert", Convert.ToBase64String(permissionsApiCertificate.GetRawCertData())); - } + options.DefaultRequestHeaders.Add("x-api-key", builder.Configuration["PermissionsApi:ApiKey"]); }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/src/Saas.Admin/readme.md b/src/Saas.Admin/readme.md index 304e53ea..5c02e854 100644 --- a/src/Saas.Admin/readme.md +++ b/src/Saas.Admin/readme.md @@ -59,12 +59,11 @@ Default values for non secret app settings can be found in [appsettings.json](Sa | ClaimToRoleTransformer:RoleClaimtype | Type of the claim to use in the new Identity, works alongside built-in | false | MyCustomRoles | | ClaimToRoleTransformer:SourceClaimType | Name of the claim custom roles are in | false | permissions | | ConnectionStrings:TenantsContext | Connection String to SQL server database used to store permission data. | true | (localdb connnection string) | -| KeyVault:PermissionsApiCert | The name of the secret in Azure Key Vault that contains a base64 encoded certificate to use for authentication with the permissions api | false | | | KeyVault:Url | KeyVault URL to pull secret values from in production | false | | | Logging:LogLevel:Default | Logging level when no configured provider is matched | false | Information | | Logging:LogLevel:Microsoft.AspNetCore | Logging level for AspNetCore logging | false | Warning | | PermissionsApi:BaseUrl | URL for downstream [Permissions API](../Saas.Identity/Saas.Permissions/readme.md) | false | | -| PermissionsApi:LocalCertificate | A Base64 encoded certificate (.CER) used to authenticate with the permissions API. | true | | +| PermissionsApi:ApiKey | API Key to use for authentication with the downstream [Permissions API](../Saas.Identity/Saas.Permissions/readme.md) | true | | ### iv. Starting the App diff --git a/src/Saas.IaC/keyVault.bicep b/src/Saas.IaC/keyVault.bicep index c29d866b..f518992b 100644 --- a/src/Saas.IaC/keyVault.bicep +++ b/src/Saas.IaC/keyVault.bicep @@ -78,18 +78,11 @@ param keyVaultName string @description('The location for all resources.') param location string -@description('The name of the Permissions Api Certificate Key Vault Secret.') -param permissionsApiCertificateSecretName string = 'admin-KeyVault--PermissionsApiCert' +@description('The name of the Permissions Api Api Key Key Vault Secret.') +param permissionsApiApiKeySecretName string = 'admin-PermissionsApi--ApiKey' -@description('The value of the Permissions Api Certificate Key Vault Secret.') -param permissionsApiCertificateSecretValue string - - -@description('The name of the Permissions Api Certificate Passphrase Key Vault Secret.') -param permissionsApiCertificatePassphraseSecretName string = 'admin-KeyVault--PermissionsApiCertPassphrase' - -@description('The value of the Permissions Api Certificate Passphrase Key Vault Secret.') -param permissionsApiCertificatePassphraseSecretValue string +@description('The value of the Permissions Api Api Key Key Vault Secret.') +param permissionsApiApiKeySecretValue string // Resource - Key Vault @@ -114,21 +107,11 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' = { // Resource - Key Vault - Secret - Permissions Api Certificate ////////////////////////////////////////////////// -resource permissionsApiCertificateSecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: permissionsApiCertificateSecretName - properties: { - value: permissionsApiCertificateSecretValue - } -} - -// Resource - Key Vault - Secret - Permissions Api Certificate Passphrase -////////////////////////////////////////////////// -resource permissionsApiCertificatePassphraseSecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { +resource permissionsApiApiKeySecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { parent: keyVault - name: permissionsApiCertificatePassphraseSecretName + name: permissionsApiApiKeySecretName properties: { - value: permissionsApiCertificatePassphraseSecretValue + value: permissionsApiApiKeySecretValue } } diff --git a/src/Saas.IaC/main.bicep b/src/Saas.IaC/main.bicep index a2795307..ce2ec062 100644 --- a/src/Saas.IaC/main.bicep +++ b/src/Saas.IaC/main.bicep @@ -7,10 +7,10 @@ param adminApiScopes string param adminApiScopeBaseUrl string @description('The tag of the container image to deploy to the Admin api app service.') -param adminApiContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-admin:v1.0' +param adminApiContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-admin:v1.1' @description('The tag of the container image to deploy to the SaaS Application api app service.') -param applicationContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-web:v1.0' +param applicationContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-web:v1.1' @description('The value of the Azure AD B2C Admin Api Client Id Key Vault Secret.') param azureAdB2cAdminApiClientIdSecretValue string @@ -57,18 +57,15 @@ param location string = resourceGroup().location @description('The Host Name of the Permissions Api to point the Admin Api to.') param permissionsApiHostName string -@description('The base64 encoded certificate to save in the keyvault for securing communication with the permissions API.') -param permissionsApiCertificateSecretValue string - -@description('The passphrase fopr the certificate to save in the keyvault for securing communication with the permissions API.') +@description('The API Key used to authenticate with the Permissions Api.') @secure() -param permissionsApiCertificatePassphraseSecretValue string +param permissionsApiApiKeySecretValue string @description('Scopes to authorize SaaS App user for the admin service.') param saasAppApiScopes string @description('The tag of the container image to deploy to the SignupAdmin app service.') -param signupAdminContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-signup:v1.0' +param signupAdminContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-signup:v1.1' @description('The SaaS Provider name.') param saasProviderName string @@ -163,8 +160,7 @@ module keyVaultModule 'keyVault.bicep' = { azureAdB2cTenantIdSecretValue: azureAdB2cTenantIdSecretValue keyVaultName: keyVaultName location: location - permissionsApiCertificateSecretValue: permissionsApiCertificateSecretValue - permissionsApiCertificatePassphraseSecretValue: permissionsApiCertificatePassphraseSecretValue + permissionsApiApiKeySecretValue: permissionsApiApiKeySecretValue } } diff --git a/src/Saas.IaC/main.json b/src/Saas.IaC/main.json index 8b5f846f..0036785d 100644 --- a/src/Saas.IaC/main.json +++ b/src/Saas.IaC/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.7.4.23292", - "templateHash": "7285554353607664512" + "templateHash": "13913189299763884879" } }, "parameters": { @@ -23,14 +23,14 @@ }, "adminApiContainerImageTag": { "type": "string", - "defaultValue": "ghcr.io/azure/azure-saas/asdk-admin:v1.0", + "defaultValue": "ghcr.io/azure/azure-saas/asdk-admin:v1.1", "metadata": { "description": "The tag of the container image to deploy to the Admin api app service." } }, "applicationContainerImageTag": { "type": "string", - "defaultValue": "ghcr.io/azure/azure-saas/asdk-web:v1.0", + "defaultValue": "ghcr.io/azure/azure-saas/asdk-web:v1.1", "metadata": { "description": "The tag of the container image to deploy to the SaaS Application api app service." } @@ -131,16 +131,10 @@ "description": "The Host Name of the Permissions Api to point the Admin Api to." } }, - "permissionsApiCertificateSecretValue": { - "type": "string", - "metadata": { - "description": "The base64 encoded certificate to save in the keyvault for securing communication with the permissions API." - } - }, - "permissionsApiCertificatePassphraseSecretValue": { + "permissionsApiApiKeySecretValue": { "type": "secureString", "metadata": { - "description": "The passphrase fopr the certificate to save in the keyvault for securing communication with the permissions API." + "description": "The API Key used to authenticate with the Permissions Api." } }, "saasAppApiScopes": { @@ -151,7 +145,7 @@ }, "signupAdminContainerImageTag": { "type": "string", - "defaultValue": "ghcr.io/azure/azure-saas/asdk-signup:v1.0", + "defaultValue": "ghcr.io/azure/azure-saas/asdk-signup:v1.1", "metadata": { "description": "The tag of the container image to deploy to the SignupAdmin app service." } @@ -547,11 +541,8 @@ "location": { "value": "[parameters('location')]" }, - "permissionsApiCertificateSecretValue": { - "value": "[parameters('permissionsApiCertificateSecretValue')]" - }, - "permissionsApiCertificatePassphraseSecretValue": { - "value": "[parameters('permissionsApiCertificatePassphraseSecretValue')]" + "permissionsApiApiKeySecretValue": { + "value": "[parameters('permissionsApiApiKeySecretValue')]" } }, "template": { @@ -561,7 +552,7 @@ "_generator": { "name": "bicep", "version": "0.7.4.23292", - "templateHash": "10345434415902719005" + "templateHash": "16792040523285546760" } }, "parameters": { @@ -736,30 +727,17 @@ "description": "The location for all resources." } }, - "permissionsApiCertificateSecretName": { - "type": "string", - "defaultValue": "admin-KeyVault--PermissionsApiCert", - "metadata": { - "description": "The name of the Permissions Api Certificate Key Vault Secret." - } - }, - "permissionsApiCertificateSecretValue": { - "type": "string", - "metadata": { - "description": "The value of the Permissions Api Certificate Key Vault Secret." - } - }, - "permissionsApiCertificatePassphraseSecretName": { + "permissionsApiApiKeySecretName": { "type": "string", - "defaultValue": "admin-KeyVault--PermissionsApiCertPassphrase", + "defaultValue": "admin-PermissionsApi--ApiKey", "metadata": { - "description": "The name of the Permissions Api Certificate Passphrase Key Vault Secret." + "description": "The name of the Permissions Api Api Key Key Vault Secret." } }, - "permissionsApiCertificatePassphraseSecretValue": { + "permissionsApiApiKeySecretValue": { "type": "string", "metadata": { - "description": "The value of the Permissions Api Certificate Passphrase Key Vault Secret." + "description": "The value of the Permissions Api Api Key Key Vault Secret." } } }, @@ -784,20 +762,9 @@ { "type": "Microsoft.KeyVault/vaults/secrets", "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('permissionsApiCertificateSecretName'))]", - "properties": { - "value": "[parameters('permissionsApiCertificateSecretValue')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('permissionsApiCertificatePassphraseSecretName'))]", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('permissionsApiApiKeySecretName'))]", "properties": { - "value": "[parameters('permissionsApiCertificatePassphraseSecretValue')]" + "value": "[parameters('permissionsApiApiKeySecretValue')]" }, "dependsOn": [ "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" diff --git a/src/Saas.IaC/main.parameters.json b/src/Saas.IaC/main.parameters.json index 6322dfdd..e55c0e7f 100644 --- a/src/Saas.IaC/main.parameters.json +++ b/src/Saas.IaC/main.parameters.json @@ -35,10 +35,7 @@ "permissionsApiHostName": { "value": "null" }, - "permissionsApiCertificateSecretValue": { - "value": "null" - }, - "permissionsApiCertificatePassphraseSecretValue": { + "permissionsApiApiKeySecretValue": { "value": "null" }, "saasAppApiScopes": { diff --git a/src/Saas.Identity/SaaS.Identity.IaC/identityKeyVault.bicep b/src/Saas.Identity/SaaS.Identity.IaC/identityKeyVault.bicep index ea95bedd..5e3d1db9 100644 --- a/src/Saas.Identity/SaaS.Identity.IaC/identityKeyVault.bicep +++ b/src/Saas.Identity/SaaS.Identity.IaC/identityKeyVault.bicep @@ -35,11 +35,11 @@ param azureAdB2cPermissionsApiClientSecretSecretName string = 'permissions-Azure @description('The value of the Azure AD B2C Permissions Api Client Secret Key Vault Secret.') param azureAdB2cPermissionsApiClientSecretSecretValue string -@description('The name of the Permissions Api SSL Thumbprint Key Vault Secret.') -param permissionsApiSslThumbprintSecretName string = 'permissions-AppSettings--SSLCertThumbprint' +@description('The name of the Permissions Api Api Key Key Vault Secret.') +param permissionsApiApiKeySecretName string = 'permissions-AppSettings--ApiKey' -@description('The value of the Permissions Api SSL Thumbprint Key Vault Secret.') -param permissionsApiSslThumbprintSecretValue string +@description('The value of the Permissions Api Api Key Key Vault Secret.') +param permissionsApiApiKeySecretValue string @description('The name of the Permissions SQL Connection String Key Vault Secret.') param permissionsSqlConnectionStringSecretName string = 'permissions-ConnectionStrings--PermissionsContext' @@ -67,13 +67,13 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' = { } } -// Resource - Key Vault - Secret - Permissions Api SSL Thumbprint +// Resource - Key Vault - Secret - Permissions Api Api Key ////////////////////////////////////////////////// resource permissionsApiSslThumbprintSecret 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { parent: keyVault - name: permissionsApiSslThumbprintSecretName + name: permissionsApiApiKeySecretName properties: { - value: permissionsApiSslThumbprintSecretValue + value: permissionsApiApiKeySecretValue } } diff --git a/src/Saas.Identity/SaaS.Identity.IaC/main.bicep b/src/Saas.Identity/SaaS.Identity.IaC/main.bicep index 0a59b17f..c451e0f8 100644 --- a/src/Saas.Identity/SaaS.Identity.IaC/main.bicep +++ b/src/Saas.Identity/SaaS.Identity.IaC/main.bicep @@ -17,14 +17,14 @@ param azureAdB2cPermissionsApiClientIdSecretValue string @description('The value of the Azure AD B2C Permissions Api Client Secret Key Vault Secret.') param azureAdB2cPermissionsApiClientSecretSecretValue string -@description('The value of the Permissions Api SSL Thumbprint Key Vault Secret.') -param permissionsApiSslThumbprintSecretValue string +@description('The value of the Permissions Api Api Key Key Vault Secret.') +param permissionsApiApiKeySecretValue string @description('The URL for the container registry to pull the docker images from') param containerRegistryUrl string = 'https://ghcr.io' @description('The tag of the container image to deploy to the permissions api app service.') -param permissionsApiContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-permissions:v1.0' +param permissionsApiContainerImageTag string = 'ghcr.io/azure/azure-saas/asdk-permissions:v1.1' @description('The location for all resources.') param location string = resourceGroup().location @@ -98,7 +98,7 @@ module identityKeyVaultModule './identityKeyVault.bicep' = { azureAdB2cTenantIdSecretValue: azureAdB2cTenantIdSecretValue azureAdB2cPermissionsApiClientIdSecretValue: azureAdB2cPermissionsApiClientIdSecretValue azureAdB2cPermissionsApiClientSecretSecretValue: azureAdB2cPermissionsApiClientSecretSecretValue - permissionsApiSslThumbprintSecretValue: permissionsApiSslThumbprintSecretValue + permissionsApiApiKeySecretValue: permissionsApiApiKeySecretValue permissionsSqlConnectionStringSecretValue: permissionsSqlModule.outputs.permissionsSqlDatabaseConnectionString } } diff --git a/src/Saas.Identity/SaaS.Identity.IaC/main.parameters.json b/src/Saas.Identity/SaaS.Identity.IaC/main.parameters.json index 4d056cf3..a54fed81 100644 --- a/src/Saas.Identity/SaaS.Identity.IaC/main.parameters.json +++ b/src/Saas.Identity/SaaS.Identity.IaC/main.parameters.json @@ -17,7 +17,7 @@ "azureAdB2cPermissionsApiClientSecretSecretValue": { "value": "null" }, - "permissionsApiSslThumbprintSecretValue": { + "permissionsApiApiKeySecretValue": { "value": "null" }, "permissionsApiContainerImageTag": { diff --git a/src/Saas.Identity/SaaS.Identity.IaC/permissionsApi.bicep b/src/Saas.Identity/SaaS.Identity.IaC/permissionsApi.bicep index 9de59be7..b41f9460 100644 --- a/src/Saas.Identity/SaaS.Identity.IaC/permissionsApi.bicep +++ b/src/Saas.Identity/SaaS.Identity.IaC/permissionsApi.bicep @@ -27,8 +27,6 @@ resource permissionsApi 'Microsoft.Web/sites@2021-03-01' = { properties: { serverFarmId: appServicePlanId httpsOnly: true - clientCertEnabled: true - clientCertMode: 'Required' siteConfig: { alwaysOn: true linuxFxVersion: 'DOCKER|${permissionsApiContainerImageTag}' diff --git a/src/Saas.Identity/Saas.IdentityProvider/policies/TrustFrameworkExtensions.xml b/src/Saas.Identity/Saas.IdentityProvider/policies/TrustFrameworkExtensions.xml index 674d98a9..fdf99f0d 100644 --- a/src/Saas.Identity/Saas.IdentityProvider/policies/TrustFrameworkExtensions.xml +++ b/src/Saas.Identity/Saas.IdentityProvider/policies/TrustFrameworkExtensions.xml @@ -57,13 +57,11 @@ {Settings:PermissionsAPIUrl} Body - - ClientCertificate - - true + ApiKeyHeader + false - + @@ -84,13 +82,11 @@ {Settings:RolesAPIUrl} Body - - ClientCertificate - - true + ApiKeyHeader + false - + diff --git a/src/Saas.Identity/Saas.IdentityProvider/scripts/B2C-Create.ps1 b/src/Saas.Identity/Saas.IdentityProvider/scripts/B2C-Create.ps1 index 0d1dfbd7..9457dd4e 100644 --- a/src/Saas.Identity/Saas.IdentityProvider/scripts/B2C-Create.ps1 +++ b/src/Saas.Identity/Saas.IdentityProvider/scripts/B2C-Create.ps1 @@ -56,7 +56,9 @@ function New-SaaSIdentityProvider { -SaasAppFQDN $userInputParams.SaasAppFQDN ` -CurrentB2CUserId $currentB2CUser.Id ` - $selfSignedCert = New-AsdkSelfSignedCertificate $userInputParams.SelfSignedCertificatePassword + + # Create Api Key + $permissionsApiKey = Get-RandomPassword -Length 32 # Deploy Bicep here @@ -67,7 +69,7 @@ function New-SaaSIdentityProvider { -B2cTenantId $createdTenantGuid ` -PermissionsApiAppRegClientId $appRegistrations.PermissionsAppReg.AppRegistrationProperties.AppId ` -PermissionsApiAppRegClientSecret $appRegistrations.PermissionsAppReg.ClientSecret ` - -PermissionsApiSelfSignedCertThumbprint $selfSignedCert.PfxThumbprint ` + -PermissionsApiApiKey $permissionsApiKey ` -SaasProviderName $userInputParams.ProviderName ` -SaasEnvironment $userInputParams.SaasEnvironment ` -SaasInstanceNumber $userInputParams.InstanceNumber ` @@ -79,8 +81,8 @@ function New-SaaSIdentityProvider { $trustFrameworkKeySetSigningKeyId = New-TrustFrameworkSigningKey $trustFrameworkKeySetEncryptionKeyId = New-TrustFrameworkEncryptionKey - # Upload cert to b2c here - $trustFrameworkKeySetClientCertificateKeyId = New-TrustFrameworkClientCertificateKey -CertificateString $selfSignedCert.PfxString -Pswd $userInputParams.SelfSignedCertificatePassword + # Upload API key to B2C policy key container + $trustFrameworkKeySetApiKeyId = New-TrustFrameworkApiKey -ApiKey $permissionsApiKey # Upload policies $configTokens = @{ @@ -89,7 +91,7 @@ function New-SaaSIdentityProvider { "{Settings:IdentityExperienceFrameworkAppId}" = "$($appRegistrations.IEFAppReg.AppRegistrationProperties.AppId)" "{Settings:PermissionsAPIUrl}" = "$($userInputParams.PermissionsApiFQDN)/api/CustomClaims/permissions" "{Settings:RolesAPIUrl}" = "$($userInputParams.PermissionsApiFQDN)/api/CustomClaims/roles" - "{Settings:RESTAPIClientCertificate}" = "$($trustFrameworkKeySetClientCertificateKeyId.Id)" + "{Settings:RESTAPIKey}" = "$($trustFrameworkKeySetApiKeyId.Id)" } Import-IEFPolicies -configTokens $configTokens @@ -106,15 +108,14 @@ function New-SaaSIdentityProvider { azureAdB2cAdminApiClientIdSecretValue = @{ value = $appRegistrations.AdminAppReg.AppRegistrationProperties.AppId } azureAdB2cDomainSecretValue = @{ value = "$($userInputParams.B2CTenantName).onmicrosoft.com" } azureAdB2cInstanceSecretValue = @{ value = "https://$($userInputParams.B2CTenantName).b2clogin.com" } - azureAdB2cSaasAppClientIdSecretValue = @{ value = $appRegistrations.SaasAppAppReg.AppRegistrationProperties.AppId } + azureAdB2cSaasAppClientIdSecretValue = @{ value = $appRegistrations.SaasAppAppReg.AppRegistrationProperties.AppId } azureAdB2cSaasAppClientSecretSecretValue = @{ value = $appRegistrations.SaasAppAppReg.ClientSecret } azureAdB2cSignupAdminClientIdSecretValue = @{ value = $appRegistrations.SignupAdminAppReg.AppRegistrationProperties.AppId } azureAdB2cSignupAdminClientSecretSecretValue = @{ value = $appRegistrations.SignupAdminAppReg.ClientSecret } - azureAdB2cTenantIdSecretValue = @{ value = $createdTenantGuid } + azureAdB2cTenantIdSecretValue = @{ value = $createdTenantGuid } permissionsApiHostName = @{ value = $userInputParams.PermissionsApiFQDN } - permissionsApiCertificateSecretValue = @{ value = $selfSignedCert.PfxString } - permissionsApiCertificatePassphraseSecretValue = @{ value = ConvertFrom-SecureString -SecureString $userInputParams.SelfSignedCertificatePassword -AsPlainText } - saasAppApiScopes = @{ value = $appRegistrations.SaasAppAppReg.AdminScopesForSaasApp -join " " } + permissionsApiApiKeySecretValue = @{ value = $permissionsApiKey } + saasAppApiScopes = @{ value = $appRegistrations.SaasAppAppReg.AdminScopesForSaasApp -join " " } saasProviderName = @{ value = $userInputParams.ProviderName } saasEnvironment = @{ value = $userInputParams.SaasEnvironment } saasInstanceNumber = @{ value = $userInputParams.InstanceNumber } @@ -183,7 +184,6 @@ function Get-UserInputParameters { InstanceNumber = Read-Host "Please enter an instance number. This number will be appended to most Azure Resources created. (e.g. 001, 002, 003)" SqlAdministratorLogin = Read-Host "Please enter the desired username for the SQL administrator account (e.g. sqladmin). Note: 'admin' is not allowed and will fail during the deployment step." SqlAdministratorLoginPassword = Read-Host -AsSecureString -Prompt "Please enter the desired password for the SQL administrator account." - SelfSignedCertificatePassword = Read-Host -AsSecureString -Prompt "Please enter the desired password for the self-signed certificate that will be generated." } $userInputParams.Add("SaasAppFQDN", "https://appapplication$($userInputParams.ProviderName)$($userInputParams.SaasEnvironment).azurewebsites.net") @@ -201,7 +201,6 @@ function Get-UserInputParameters { -InstanceNumber $userInputParams.InstanceNumber ` -SqlAdministratorLogin $userInputParams.SqlAdministratorLogin ` -SqlAdministratorLoginPassword $userInputParams.SqlAdministratorLoginPassword ` - -SelfSignedCertificatePassword $userInputParams.SelfSignedCertificatePassword ` return $userInputParams @@ -246,10 +245,8 @@ function Confirm-UserInputParameters { [string] $SqlAdministratorLogin, [Parameter(Mandatory=$true)] - [securestring] $SqlAdministratorLoginPassword, + [securestring] $SqlAdministratorLoginPassword - [Parameter(Mandatory=$true)] - [securestring] $SelfSignedCertificatePassword ) return @@ -400,24 +397,24 @@ function New-TrustFrameworkEncryptionKey { return $trustFrameworkKeySet } -function New-TrustFrameworkClientCertificateKey { +function New-TrustFrameworkApiKey { param ( - [string] $CertificateString, - [Security.SecureString] $Pswd + [string] $ApiKey ) - Write-Host "Creating client certificate policy..." - $trustFrameworkKeySetName = "RestApiClientCertificate" + Write-Host "Creating new API key..." + $trustFrameworkKeySetName = "RestApiKey" try { $trustFrameworkKeySet = New-MgTrustFrameworkKeySet -Id $trustFrameworkKeySetName $params = @{ - Key = $CertificateString - Password = ConvertFrom-SecureString -SecureString $Pswd -AsPlainText + TrustFrameworkKeySetId = $trustFrameworkKeySet.Id + k = $ApiKey + use = "sig" } - Invoke-MgUploadTrustFrameworkKeySetPkcs12 -TrustFrameworkKeySetId $trustFrameworkKeySet.Id -BodyParameter $params + Invoke-MgUploadTrustFrameworkKeySetSecret -TrustFrameworkKeySetId $trustFrameworkKeySet.Id -BodyParameter $params } catch { - Write-Warning "Error on creating client certificate policy. Error: $_" + Write-Warning "Error on creating API Key policy. Error: $_" } return $trustFrameworkKeySet @@ -521,7 +518,7 @@ function Invoke-IdentityBicepDeployment { [string] $B2cTenantId, [string] $PermissionsApiAppRegClientId, [string] $PermissionsApiAppRegClientSecret, - [string] $PermissionsApiSelfSignedCertThumbprint, + [string] $PermissionsApiApiKey, [string] $SaasProviderName, [string] $SaasEnvironment, [string] $SaasInstanceNumber, @@ -536,7 +533,7 @@ function Invoke-IdentityBicepDeployment { azureAdB2cTenantIdSecretValue = $B2cTenantId azureAdB2cPermissionsApiClientIdSecretValue = $PermissionsApiAppRegClientId azureAdB2cPermissionsApiClientSecretSecretValue = $PermissionsApiAppRegClientSecret - permissionsApiSslThumbprintSecretValue = $PermissionsApiSelfSignedCertThumbprint + permissionsApiApiKeySecretValue = $PermissionsApiApiKey saasProviderName = $SaasProviderName saasEnvironment = $SaasEnvironment saasInstanceNumber = $SaasInstanceNumber @@ -558,37 +555,6 @@ function Invoke-IdentityBicepDeployment { } -function New-AsdkSelfSignedCertificate { - param ( - [securestring] $CertificatePassword - ) - - try { - Write-Host "Creating self signed certificate..." - - # We are using OpenSSL to create a self signed certificate because the PKI powershell module is not supported on Powershell Core yet - - ## Generate Certificate in .crt/.key format - openssl req -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out certificate.crt -keyout certificate.key -subj "/CN=*.azurewebsites.net" - - # Convert Certificate to .pfx format with the private key - # As mentioned in our documentation, self signed certificates are not suitable for anything other than testing. Do not use this certificate in production. - $pswd = ConvertFrom-SecureString -SecureString $CertificatePassword -AsPlainText - openssl pkcs12 -export -out selfSignedCertificate.pfx -inkey certificate.key -in certificate.crt -password pass:$pswd - - # Get the thumbprint of the generated certificate - $pfxThumbprint = $(openssl pkcs12 -in selfSignedCertificate.pfx -nodes -passin pass:$pswd | openssl x509 -noout -fingerprint) -replace "SHA1 Fingerprint=", "" -replace ":", "" - $pfxBytes = Get-Content "selfSignedCertificate.pfx" -AsByteStream - $pfxString = [System.Convert]::ToBase64String($pfxBytes) - } - catch { - Write-Error "An error occurred generating the self signed certificate: $_.Exception.Message" - throw - } - - return @{ PfxString = $pfxString; PfxThumbprint = $pfxThumbprint; Pswd = $pswd } -} - # Helper Function called by Install-AppRegistrations function New-AppRegistration { param ( @@ -1164,4 +1130,27 @@ function Write-OutputFile { } +function Get-RandomPassword { + param ( + [Parameter(Mandatory)] + [int] $length + ) + + Write-Host "Generating new API key for securing the permissions API..." + $charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{]+-[*=@)}$^(_!#?>/|.'.ToCharArray() + $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider + $bytes = New-Object byte[]($length) + + $rng.GetBytes($bytes) + + $result = New-Object char[]($length) + + for ($i = 0 ; $i -lt $length ; $i++) { + $result[$i] = $charSet[$bytes[$i]%$charSet.Length] + } + + Write-Host "Api Key Generated" + return (-join $result) +} + New-SaaSIdentityProvider diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/CustomClaimsController.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/CustomClaimsController.cs index c02e770f..87933d4b 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/CustomClaimsController.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/CustomClaimsController.cs @@ -6,10 +6,7 @@ namespace Saas.Permissions.Service.Controllers; [Route("api/[controller]")] - [ApiController] -// Specify that this controller should use Certificate Based Auth. Certificate auth is required for fetching custom claims from B2C. -[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults.AuthenticationScheme)] public class CustomClaimsController : ControllerBase { private readonly IPermissionsService _permissionsService; diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/PermissionsController.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/PermissionsController.cs index 9ec80010..ff2c8c43 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/PermissionsController.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Controllers/PermissionsController.cs @@ -8,7 +8,6 @@ namespace Saas.Permissions.Service.Controllers; [Route("api/[controller]")] [ApiController] -[Authorize(AuthenticationSchemes = CertificateAuthenticationDefaults .AuthenticationScheme)] public class PermissionsController : ControllerBase { private readonly IPermissionsService _permissionsService; diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/AppSettings/AppSettings.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/AppSettings/AppSettings.cs index f57e21a5..9077a051 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/AppSettings/AppSettings.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/AppSettings/AppSettings.cs @@ -2,5 +2,5 @@ public class AppSettings { - public string SSLCertThumbprint { get; set; } = string.Empty; + public string ApiKey { get; set; } = string.Empty; } diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/UnauthorizedResponse.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/UnauthorizedResponse.cs new file mode 100644 index 00000000..2cfccb6f --- /dev/null +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Models/UnauthorizedResponse.cs @@ -0,0 +1,12 @@ +namespace Saas.Permissions.Service.Models; + + + +public class UnauthorizedResponse +{ + public UnauthorizedResponse(string _error) + { + Error = _error; + } + public string Error { get; set; } +} diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Program.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Program.cs index 29afd51a..0d344181 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Program.cs +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Program.cs @@ -1,6 +1,4 @@ using Azure.Identity; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.HttpOverrides; using Saas.Permissions.Service.Data; using Saas.Permissions.Service.Interfaces; @@ -36,6 +34,7 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); + builder.Services.AddDbContext(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("PermissionsContext")); @@ -43,64 +42,20 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddSingleton(); - -// Look for certificate forwarded by the web server on X-Arr-Client-Cert -builder.Services.AddCertificateForwarding(options => { options.CertificateHeader = "X-ARR-ClientCert"; }); - -// This is required for auth to work correctly when running in a docker container because of SSL Termination -// Remove this and the subsequent app.UseForwardedHeaders() line below if you choose to run the app without using containers -builder.Services.Configure(options => -{ - options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; - options.ForwardedProtoHeaderName = "X-Forwarded-Proto"; -}); - -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - // Add Certificate Validation for authentication from azure b2c. - .AddCertificate(options => - { - // It is not reccomended to use self signed certificates for production scenarios. - // https://docs.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0#configure-certificate-validation - options.AllowedCertificateTypes = CertificateTypes.All; - options.Events = new CertificateAuthenticationEvents - { - OnCertificateValidated = context => - { - var validationService = context.HttpContext.RequestServices - .GetRequiredService(); - - if (validationService.ValidateCertificate(context.ClientCertificate)) - { - context.Success(); - } - else - { - context.Fail("Cert Thumbprint is Invalid"); - } - return Task.CompletedTask; - } - }; - }); - -builder.Services.AddAuthorization(); var app = builder.Build(); app.ConfigureDatabase(); -// Configure the HTTP request pipeline. - +if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); +} app.UseHttpsRedirection(); - -// https://docs.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0#configure-certificate-validation -app.UseCertificateForwarding(); app.UseForwardedHeaders(); -app.UseAuthentication(); -app.UseAuthorization(); +// Adds middleware to check for the presence of an API Key +app.UseMiddleware(); app.MapControllers(); diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Services/CertificateValidationService.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Services/CertificateValidationService.cs deleted file mode 100644 index be76c1c0..00000000 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Services/CertificateValidationService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; -using Saas.Permissions.Service.Interfaces; -using Saas.Permissions.Service.Models.AppSettings; -using System.Security.Cryptography.X509Certificates; -namespace Saas.Permissions.Service.Services; - -public class CertificateValidationService : ICertificateValidationService -{ - private readonly AppSettings _appSettings; - private readonly ILogger _logger; - public CertificateValidationService(IOptions appSettings, ILogger logger) - { - _appSettings = appSettings.Value; - _logger = logger; - } - public bool ValidateCertificate(X509Certificate2 clientCertificate) - { - // Insert any other custom certificate validation logic here - //_logger should be used along with this logic. - - // Do not check your certificate thumbprint into your git repository. - // Another option would be to load in your certificate thumbprint from azure keyvault. - var expectedCertificateThumbPrint = _appSettings.SSLCertThumbprint; - - return clientCertificate.Thumbprint == expectedCertificateThumbPrint; - } -} diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Utilities/ApiKeyMiddleware.cs b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Utilities/ApiKeyMiddleware.cs new file mode 100644 index 00000000..15904e34 --- /dev/null +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Utilities/ApiKeyMiddleware.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Saas.Permissions.Service.Models; +using Saas.Permissions.Service.Models.AppSettings; +namespace Saas.Permissions.Service.Utilities; + +public class ApiKeyMiddleware { + private readonly RequestDelegate _next; + private const string API_KEY = "x-api-key"; + private readonly AppSettings _appSettings; + public ApiKeyMiddleware(IOptions appSettings, RequestDelegate next) { + _next = next; + _appSettings = appSettings.Value; + } + public async Task InvokeAsync(HttpContext context) { + + if (!context.Request.Headers.TryGetValue(API_KEY, out var extractedApiKey)) { + context.Response.StatusCode = 401; + await context.Response.WriteAsJsonAsync(new UnauthorizedResponse($"API Key must be provided on the {API_KEY} header")); + return; + } + + var appSettings = context.RequestServices.GetRequiredService(); + var apiKey = _appSettings.ApiKey; + + if (!apiKey.Equals(extractedApiKey)) { + context.Response.StatusCode = 401; + await context.Response.WriteAsJsonAsync(new UnauthorizedResponse("API Key provided was invalid.")); + return; + } + await _next(context); + } +} \ No newline at end of file diff --git a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/appsettings.json b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/appsettings.json index 4d8b1885..0d70828e 100644 --- a/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/appsettings.json +++ b/src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service/appsettings.json @@ -1,6 +1,4 @@ { - "AppSettings": { - }, "KeyVault": { "Url": "" }, diff --git a/src/Saas.Identity/Saas.Permissions/readme.md b/src/Saas.Identity/Saas.Permissions/readme.md index 51780eec..17549fed 100644 --- a/src/Saas.Identity/Saas.Permissions/readme.md +++ b/src/Saas.Identity/Saas.Permissions/readme.md @@ -38,7 +38,7 @@ Default values for non secret app settings can be found in [appsettings.json](Sa | AppSetting Key | Description | Secret | Default Value | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------ | ----------------------------- | -| AppSettings:SSLCertThumbprint | The certificate thumbprint used to validate the certificate forwarded to the application via the web server. | true | | +| AppSettings:ApiKey | The API Key used to secure the permissions service | true | | | AzureAdB2C:ClientId | The service client corresponding to the Signup Admin application | true | | | AzureAdB2C:Domain | Domain name for the Azure AD B2C instance | true | | | AzureAdB2C:Instance | URL for the root of the Azure AD B2C instance | true | |