diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..f1839cf
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,12 @@
+# Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you to agree to a
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
+the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
+
+When you submit a pull request, a CLA bot automatically determines whether you need to provide
+a CLA and decorate the pull request appropriately (for example, status check, comment). Follow the instructions provided by the bot. You need to do this step once across all repos using our CLA.
+
+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 more questions or comments.
diff --git a/README.md b/README.md
index 5cd7cec..512959c 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,36 @@
-# Project
+# Workload templates and sample application for Sovereign Landing Zone
-> This repo has been populated by an initial template to help get you started. Please
-> make sure to update the content to build a great experience for community-building.
+## Overview
-As the maintainer of this project, please make a few updates:
+[Sovereign Landing Zone (SLZ) ](https://github.com/Azure/sovereign-landing-zone) provides an environment offering guardrails through policies and policy sets, security-enforcement, and consistent baseline infrastructure for deploying workloads and applications. SLZ is based on [Azure Landing Zones](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) and extends it with guardrails and security controls specific to sovereignty requirements.
-- Improving this README.MD file to provide a great experience
-- Updating SUPPORT.MD with content about this project's support experience
-- Understanding the security reporting process in SECURITY.MD
-- Remove this section from the README
+To help accelerate customers time-to-value while assisting them in meeting their compliance objectives, the [Microsoft Cloud for Sovereignty](https://learn.microsoft.com/industry/sovereignty) includes ready-to-use workload templates that can be consistently deployed and operated in a repeatable manner. The workload templates are aligned with [Sovereignty Policy Baseline](https://github.com/Azure/sovereign-landing-zone/blob/main/docs/scenarios/Sovereignty-Policy-Baseline.md), [Cloud for Sovereignty policy portfolio](https://github.com/Azure/cloud-for-sovereignty-policy-portfolio), and [Azure Landing Zone default policies](https://github.com/Azure/Enterprise-Scale/wiki/ALZ-Policies).
-## Contributing
+We're introducing two templates, and a sample sovereign application for learning purposes and to validate the functionality of SLZ policy sets and their enforcement of the confidentiality of services within the Sovereign Landing Zone.
-This project welcomes contributions and suggestions. Most contributions require you to agree to a
-Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
-the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
+You can deploy all applications using the PowerShell and Bicep, and they are fully compatible with SLZ. To learn more about the advantages of using these templates, refer to the following links:
-When you submit a pull request, a CLA bot will automatically determine whether you need to provide
-a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
-provided by the bot. You will only need to do this once across all repos using our CLA.
+1. [**Azure Lighthouse template**](./workloadAccelerators/lighthouse/docs/lighthouseAccelerator.md)
+2. [**Azure Confidential Virtual Machine AMD-SNP template**](./workloadAccelerators/confidentialVirtualMachine/docs/cvmAccelerator.md)
+3. [**Confidential sample application**](./sovereignApplications/confidential/hrAppWorkload/README.md)
-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.
+## Shared responsibility and customer responsibilities
+
+To ensure your data is secure and your privacy controls are addressed, we recommend that you follow a set of best practices when deploying into Azure:
+
+- [Azure security best practices and patterns](https://learn.microsoft.com/azure/security/fundamentals/best-practices-and-patterns)
+- [Microsoft Services in Cybersecurity](https://learn.microsoft.com/azure/security/fundamentals/cyber-services)
+
+Protecting your data also requires that all aspects of your security and compliance program include your cloud infrastructure and data. The following guidance can help you to secure your deployment.
## Trademarks
-This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
-trademarks or logos is subject to and must follow
-[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
+This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
+trademarks or logos is subject to and must follow
+[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.
+
+## Preview Notice
+
+**Preview Terms**. The Sovereign Landing Zone workload templates and sample application preview (the "PREVIEW") are licensed to you as part of your [Azure subscription](https://azure.microsoft.com/support/legal/) and subject to terms applicable to "Previews" as detailed in the Universal License Terms for Online Services section of the Microsoft Product Terms and the [Microsoft Products and Services Data Protection Addendum ("DPA")](https://www.microsoft.com/licensing/terms/welcome/welcomepage). AS STATED IN THOSE TERMS, PREVIEWS ARE PROVIDED "AS-IS," "WITH ALL FAULTS," AND "AS AVAILABLE," AND ARE EXCLUDED FROM THE SERVICE LEVEL AGREEMENTS AND LIMITED WARRANTY. Previews may employ lesser or different privacy and security measures than those typically present in Azure Services. Unless otherwise noted, you should not use Previews to process Personal Data or other data that is subject to legal or regulatory compliance requirements. The following terms in the [DPA](https://www.microsoft.com/licensing/docs/view/Microsoft-Products-and-Services-Data-Protection-Addendum-DPA) do not apply to Previews: Processing of Personal Data; GDPR, Data Security, and HIPAA Business Associate. We may change or discontinue Previews at any time without notice. We also may choose not to release a Preview into General Availability.
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
index b3c89ef..e138ec5 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,20 +1,20 @@
-
+
## Security
-Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
-If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
-Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
-If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
-You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
@@ -28,7 +28,7 @@ Please include the requested information listed below (as much as you can provid
This information will help us triage your report more quickly.
-If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
## Preferred Languages
@@ -36,6 +36,6 @@ We prefer all communications to be in English.
## Policy
-Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
diff --git a/SUPPORT.md b/SUPPORT.md
index 291d4d4..4219f0e 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -1,24 +1,9 @@
-# TODO: The maintainer of this repo has not yet edited this file
-
-**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
-
-- **No CSS support:** Fill out this template with information about how to file issues and get help.
-- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps.
-- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide.
-
-*Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
-
# Support
## How to file issues and get help
-This project uses GitHub Issues to track bugs and feature requests. Please search the existing
-issues before filing new issues to avoid duplicates. For new issues, file your bug or
-feature request as a new Issue.
-
-For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
-FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
-CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
+This project uses [GitHub issues](https://github.com/Azure/cloud-for-sovereignty-quickstarts/issues) to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. Please provide as much information as possible when filing an issue. Please include screenshots if possible.
+Project maintainers will aim to respond within 5 business days.
## Microsoft Support Policy
diff --git a/common/CRML/README.md b/common/CRML/README.md
new file mode 100644
index 0000000..723fee0
--- /dev/null
+++ b/common/CRML/README.md
@@ -0,0 +1,9 @@
+# Why Does This Directory Exist & Contain Other Bicep Modules?
+
+This directory exists to host modules that are **not** specific to the Azure Landing Zones modules that are contained within the `infra-as-code/bicep/modules` directory.
+
+The modules inside this directory, `infra-as-code/bicep/CRML` are modules that we are potentially planning to remove from this repo and migrate/consume them from the [Common Azure Resource Modules Library repo](https://github.com/Azure/ResourceModules) as part of future releases, and when features such as the Bicep Public Module Registry are available.
+
+> These are plans/aspirations which are not confirmed and might change, but we are sharing them for clarity and planning purposes đź‘Ť
+
+These modules are consumed and called by other modules within this repo. For example, the `customerUsageAttribution` module is called in all modules as you can see from each of those modules `.bicep` files.
\ No newline at end of file
diff --git a/common/CRML/customerUsageAttribution/README.md b/common/CRML/customerUsageAttribution/README.md
new file mode 100644
index 0000000..e249ef0
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/README.md
@@ -0,0 +1,25 @@
+# Module: PID
+
+This module creates a blank deployment which will be called from other modules. The purpose of this deployment is to create a deployment name to be used for Azure [customer usage attribution](https://learn.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution). To disable this, please see [How to disable Telemetry Tracking Using Customer Usage Attribution (PID)](https://github.com/Azure/ALZ-Bicep/wiki/CustomerUsage)
+
+This module does not deploy any resources
+
+## Parameters
+
+This module does not require any inputs
+
+| Parameter | Type | Default | Description | Requirement | Example |
+| --------- | ---- | ------- | ----------- | ----------- | ------- |
+
+
+## Outputs
+
+The module does not generate any outputs
+
+| Output | Type | Example |
+| ------ | ---- | ------- |
+
+## Deployment
+
+This module is intended to be called from other modules as a reusable resource.
+
diff --git a/common/CRML/customerUsageAttribution/bicepconfig.json b/common/CRML/customerUsageAttribution/bicepconfig.json
new file mode 100644
index 0000000..dceea76
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/bicepconfig.json
@@ -0,0 +1,88 @@
+{
+ "analyzers": {
+ "core": {
+ "enabled": true,
+ "verbose": false,
+ "rules": {
+ "adminusername-should-not-be-literal": {
+ "level": "error"
+ },
+ "no-hardcoded-env-urls": {
+ "level": "error"
+ },
+ "no-unnecessary-dependson": {
+ "level": "error"
+ },
+ "no-unused-params": {
+ "level": "error"
+ },
+ "no-unused-vars": {
+ "level": "error"
+ },
+ "outputs-should-not-contain-secrets": {
+ "level": "error"
+ },
+ "prefer-interpolation": {
+ "level": "error"
+ },
+ "secure-parameter-default": {
+ "level": "error"
+ },
+ "simplify-interpolation": {
+ "level": "error"
+ },
+ "protect-commandtoexecute-secrets": {
+ "level": "error"
+ },
+ "use-stable-vm-image": {
+ "level": "error"
+ },
+ "explicit-values-for-loc-params": {
+ "level": "error"
+ },
+ "no-hardcoded-location": {
+ "level": "error"
+ },
+ "no-loc-expr-outside-params": {
+ "level": "error"
+ },
+ "max-outputs": {
+ "level": "error"
+ },
+ "max-params": {
+ "level": "error"
+ },
+ "max-resources": {
+ "level": "error"
+ },
+ "max-variables": {
+ "level": "error"
+ },
+ "artifacts-parameters":{
+ "level": "error"
+ },
+ "no-unused-existing-resources":{
+ "level": "error"
+ },
+ "prefer-unquoted-property-names":{
+ "level": "error"
+ },
+ "secure-params-in-nested-deploy":{
+ "level": "error"
+ },
+ "secure-secrets-in-params":{
+ "level": "error"
+ },
+ "use-recent-api-versions":{
+ "level": "error"
+ },
+ "use-resource-id-functions":{
+ "level": "error"
+ },
+ "use-stable-resource-identifiers":{
+ "level": "error"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/CRML/customerUsageAttribution/cuaIdManagementGroup.bicep b/common/CRML/customerUsageAttribution/cuaIdManagementGroup.bicep
new file mode 100644
index 0000000..f58ed53
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/cuaIdManagementGroup.bicep
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to add the customer usage attribution (PID) to Management Group deployments.
+DESCRIPTION: This module will create a deployment at the management group level which will add the unique PID and location as the deployment name
+AUTHOR/S: shaunjacob
+VERSION: 1.0.0
+*/
+
+targetScope = 'managementGroup'
+
+// This is an empty deployment by design
+// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution
diff --git a/common/CRML/customerUsageAttribution/cuaIdResourceGroup.bicep b/common/CRML/customerUsageAttribution/cuaIdResourceGroup.bicep
new file mode 100644
index 0000000..757adc9
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/cuaIdResourceGroup.bicep
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to add the customer usage attribution (PID) to Resource Group deployments.
+DESCRIPTION: This module will create a deployment at the Resource Group level which will add the unique PID and location as the deployment name
+AUTHOR/S: shaunjacob
+VERSION: 1.0.0
+*/
+
+targetScope = 'resourceGroup'
+
+// This is an empty deployment by design
+// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution
diff --git a/common/CRML/customerUsageAttribution/cuaIdSubscription.bicep b/common/CRML/customerUsageAttribution/cuaIdSubscription.bicep
new file mode 100644
index 0000000..613790c
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/cuaIdSubscription.bicep
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to add the customer usage attribution (PID) to Subscription deployments.
+DESCRIPTION: This module will create a deployment at the Subscription level which will add the unique PID and location as the deployment name
+AUTHOR/S: shaunjacob
+VERSION: 1.0.0
+*/
+
+targetScope = 'subscription'
+
+// This is an empty deployment by design
+// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution
diff --git a/common/CRML/customerUsageAttribution/cuaIdTenant.bicep b/common/CRML/customerUsageAttribution/cuaIdTenant.bicep
new file mode 100644
index 0000000..7117daa
--- /dev/null
+++ b/common/CRML/customerUsageAttribution/cuaIdTenant.bicep
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to add the customer usage attribution (PID) to Tenant deployments.
+DESCRIPTION: This module will create a deployment at the Tenant level which will add the unique PID and location as the deployment name
+AUTHOR/S: shaunjacob
+VERSION: 1.0.0
+*/
+
+targetScope = 'tenant'
+
+// This is an empty deployment by design
+// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution
diff --git a/common/Microsoft.ManagedIdentity/userAssignedIdentities/.bicep/nested_roleAssignments.bicep b/common/Microsoft.ManagedIdentity/userAssignedIdentities/.bicep/nested_roleAssignments.bicep
new file mode 100644
index 0000000..8f5696c
--- /dev/null
+++ b/common/Microsoft.ManagedIdentity/userAssignedIdentities/.bicep/nested_roleAssignments.bicep
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: Assign roleAssignments to the principalIds.
+*/
+@sys.description('Required. The IDs of the principals to assign the role to.')
+param parPrincipalIds array
+
+@sys.description('Required. The name of the role to assign. If it cannot be found you can specify the role definition ID instead.')
+param parRoleDefinitionIdOrName string
+
+@sys.description('Required. The resource ID of the resource to apply the role assignment to.')
+param parResourceId string
+
+@sys.description('Optional. The principal type of the assigned principal ID.')
+@allowed([
+ 'ServicePrincipal'
+ 'Group'
+ 'User'
+ 'ForeignGroup'
+ 'Device'
+ ''
+])
+param parPrincipalType string = ''
+
+@sys.description('Optional. The description of the role assignment.')
+param parDescription string = ''
+
+@sys.description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container"')
+param parCondition string = ''
+
+@sys.description('Optional. Version of the condition.')
+@allowed([
+ '2.0'
+])
+param parConditionVersion string = '2.0'
+
+@sys.description('Optional. Id of the delegated managed identity resource.')
+param parDelegatedManagedIdentityResourceId string = ''
+
+var varBuiltInRoleNames = {
+ Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
+ Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
+ Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
+ 'Log Analytics Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')
+ 'Log Analytics Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')
+ 'Managed Application Contributor Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '641177b8-a67a-45b9-a033-47bc880bb21e')
+ 'Managed Application Operator Role': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c7393b34-138c-406f-901b-d8cf2b17e6ae')
+ 'Managed Applications Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b9331d33-8a36-4f8c-b097-4f54124fdb44')
+ 'Managed Identity Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59')
+ 'Managed Identity Operator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830')
+ 'Monitoring Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')
+ 'Monitoring Metrics Publisher': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')
+ 'Monitoring Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')
+ 'Resource Policy Contributor': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')
+ 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')
+}
+
+resource resUserMsi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
+ name: any(last(split(parResourceId, '/')))
+}
+
+resource resRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for principalId in parPrincipalIds: {
+ name: guid(resUserMsi.id, principalId, parRoleDefinitionIdOrName)
+ properties: {
+ description: parDescription
+ roleDefinitionId: contains(varBuiltInRoleNames, parRoleDefinitionIdOrName) ? varBuiltInRoleNames[parRoleDefinitionIdOrName] : parRoleDefinitionIdOrName
+ principalId: principalId
+ principalType: !empty(parPrincipalType) ? any(parPrincipalType) : null
+ condition: !empty(parCondition) ? parCondition : null
+ conditionVersion: !empty(parConditionVersion) && !empty(parCondition) ? parConditionVersion : null
+ delegatedManagedIdentityResourceId: !empty(parDelegatedManagedIdentityResourceId) ? parDelegatedManagedIdentityResourceId : null
+ }
+ scope: resUserMsi
+}]
diff --git a/common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep b/common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep
new file mode 100644
index 0000000..1b8f2a6
--- /dev/null
+++ b/common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: Creates a user assigned identity and optionally assigns RBAC roles to it.
+*/
+
+@description('Optional. Name of the User Assigned Identity.')
+param parName string = guid(resourceGroup().id)
+
+@description('Optional. Location for all resources.')
+param parLocation string = resourceGroup().location
+
+@allowed([
+ ''
+ 'CanNotDelete'
+ 'ReadOnly'
+])
+@description('Optional. Specify the type of lock.')
+param parLock string = ''
+
+@description('Optional. Array of role assignment objects that contain the \'roleDefinitionIdOrName\' and \'principalId\' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.')
+param parRoleAssignments array = []
+
+@description('Optional. Tags of the resource.')
+param parTags object = {}
+
+@description('Optional. Enable telemetry via the Customer Usage Attribution ID (GUID).')
+param parEnableDefaultTelemetry bool = true
+
+// Create default telemetry deployment
+resource resDefaultTelemetry 'Microsoft.Resources/deployments@2022-09-01' = if (parEnableDefaultTelemetry) {
+ name: 'pid-47ed15a6-730a-4827-bcb4-0fd963ffbd82-${uniqueString(deployment().name, parLocation)}'
+ properties: {
+ mode: 'Incremental'
+ template: {
+ '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
+ contentVersion: '1.0.0.0'
+ resources: []
+ }
+ }
+}
+
+// Create user assigned identity
+resource resUserMsi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
+ name: parName
+ location: parLocation
+ tags: parTags
+}
+
+// Create locks on user assigned identity
+resource resUserMsiLock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(parLock)) {
+ name: '${resUserMsi.name}-${parLock}-lock'
+ properties: {
+ level: any(parLock)
+ notes: parLock == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot modify the resource or child resources.'
+ }
+ scope: resUserMsi
+}
+
+// Create role assignments
+module modUserMsiRoleAssignments '.bicep/nested_roleAssignments.bicep' = [for (roleAssignment, index) in parRoleAssignments: {
+ name: '${uniqueString(deployment().name, parLocation)}-UserMSI-Rbac-${index}'
+ params: {
+ parDescription: contains(roleAssignment, 'description') ? roleAssignment.description : ''
+ parPrincipalIds: roleAssignment.principalIds
+ parPrincipalType: contains(roleAssignment, 'principalType') ? roleAssignment.principalType : ''
+ parRoleDefinitionIdOrName: roleAssignment.roleDefinitionIdOrName
+ parCondition: contains(roleAssignment, 'condition') ? roleAssignment.condition : ''
+ parDelegatedManagedIdentityResourceId: contains(roleAssignment, 'delegatedManagedIdentityResourceId') ? roleAssignment.delegatedManagedIdentityResourceId : ''
+ parResourceId: resUserMsi.id
+ }
+}]
+
+@description('The name of the user assigned identity.')
+output outName string = resUserMsi.name
+
+@description('The resource ID of the user assigned identity.')
+output outResourceId string = resUserMsi.id
+
+@description('The principal ID of the user assigned identity.')
+output outPrincipalId string = resUserMsi.properties.principalId
+
+@description('The resource group the user assigned identity was deployed into.')
+output outResourceGroupName string = resourceGroup().name
+
+@description('The location the resource was deployed into.')
+output outLocation string = resUserMsi.location
diff --git a/common/Microsoft.ManagedIdentity/userAssignedIdentities/readme.md b/common/Microsoft.ManagedIdentity/userAssignedIdentities/readme.md
new file mode 100644
index 0000000..01df578
--- /dev/null
+++ b/common/Microsoft.ManagedIdentity/userAssignedIdentities/readme.md
@@ -0,0 +1,212 @@
+# User Assigned Identities `[Microsoft.ManagedIdentity/userAssignedIdentities]`
+
+This module deploys a user assigned managed identity, applies a resource lock on it, and performs RBAC role assignments all together.
+
+## Navigation
+
+- [Resource types](#Resource-types)
+- [Parameters](#Parameters)
+- [Outputs](#Outputs)
+- [Cross-referenced modules](#Cross-referenced-modules)
+- [Deployment examples](#Deployment-examples)
+
+## Resource types
+
+| Resource Type | API Version |
+| :-- | :-- |
+| `Microsoft.Authorization/locks` | [2017-04-01](https://docs.microsoft.com/azure/templates/Microsoft.Authorization/2017-04-01/locks) |
+| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://docs.microsoft.com/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) |
+| `Microsoft.ManagedIdentity/userAssignedIdentities` | [2018-11-30](https://docs.microsoft.com/azure/templates/Microsoft.ManagedIdentity/2018-11-30/userAssignedIdentities) |
+
+## Parameters
+
+**Optional parameters**
+| Parameter Name | Type | Default Value | Allowed Values | Description |
+| :-- | :-- | :-- | :-- | :-- |
+| `enableDefaultTelemetry` | bool | `True` | | Enable telemetry via the Customer Usage Attribution ID (GUID). |
+| `location` | string | `[resourceGroup().location]` | | Location for all resources. |
+| `lock` | string | `''` | `['', CanNotDelete, ReadOnly]` | Specify the type of lock. |
+| `name` | string | `[guid(resourceGroup().id)]` | | Name of the User Assigned Identity. |
+| `roleAssignments` | array | `[]` | | Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalId' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. |
+| `tags` | object | `{object}` | | Tags of the resource. |
+
+
+### Parameter Usage: `roleAssignments`
+
+Create a role assignment for the given resource. If you want to assign a service principal / managed identity that is created in the same deployment, make sure to also specify the `'principalType'` parameter and set it to `'ServicePrincipal'`. This will ensure the role assignment waits for the principal's propagation in Azure.
+
+
+
+Parameter JSON format
+
+```json
+"roleAssignments": {
+ "value": [
+ {
+ "roleDefinitionIdOrName": "Reader",
+ "description": "Reader Role Assignment",
+ "principalIds": [
+ "12345678-1234-1234-1234-123456789012", // object 1
+ "78945612-1234-1234-1234-123456789012" // object 2
+ ]
+ },
+ {
+ "roleDefinitionIdOrName": "/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11",
+ "principalIds": [
+ "12345678-1234-1234-1234-123456789012" // object 1
+ ],
+ "principalType": "ServicePrincipal"
+ }
+ ]
+}
+```
+
+
+
+
+
+Bicep format
+
+```bicep
+roleAssignments: [
+ {
+ roleDefinitionIdOrName: 'Reader'
+ description: 'Reader Role Assignment'
+ principalIds: [
+ '12345678-1234-1234-1234-123456789012' // object 1
+ '78945612-1234-1234-1234-123456789012' // object 2
+ ]
+ }
+ {
+ roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'
+ principalIds: [
+ '12345678-1234-1234-1234-123456789012' // object 1
+ ]
+ principalType: 'ServicePrincipal'
+ }
+]
+```
+
+
+
+
+### Parameter Usage: `tags`
+
+Tag names and tag values can be provided as needed. A tag can be left without a value.
+
+
+
+Parameter JSON format
+
+```json
+"tags": {
+ "value": {
+ "Environment": "Non-Prod",
+ "Contact": "test.user@testcompany.com",
+ "PurchaseOrder": "1234",
+ "CostCenter": "7890",
+ "ServiceName": "DeploymentValidation",
+ "Role": "DeploymentValidation"
+ }
+}
+```
+
+
+
+
+
+Bicep format
+
+```bicep
+tags: {
+ Environment: 'Non-Prod'
+ Contact: 'test.user@testcompany.com'
+ PurchaseOrder: '1234'
+ CostCenter: '7890'
+ ServiceName: 'DeploymentValidation'
+ Role: 'DeploymentValidation'
+}
+```
+
+
+
+
+## Outputs
+
+| Output Name | Type | Description |
+| :-- | :-- | :-- |
+| `location` | string | The location the resource was deployed into. |
+| `name` | string | The name of the user assigned identity. |
+| `principalId` | string | The principal ID of the user assigned identity. |
+| `resourceGroupName` | string | The resource group the user assigned identity was deployed into. |
+| `resourceId` | string | The resource ID of the user assigned identity. |
+
+## Cross-referenced modules
+
+_None_
+
+## Deployment examples
+
+The following module usage examples are retrieved from the content of the files hosted in the module's `.test` folder.
+ >**Note**: The name of each example is based on the name of the file from which it is taken.
+
+ >**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order.
+
+
Example 1: Parameters
+
+
+
+via Bicep module
+
+```bicep
+module userAssignedIdentities './Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep' = {
+ name: '${uniqueString(deployment().name)}-UserAssignedIdentities'
+ params: {
+ lock: 'CanNotDelete'
+ name: '<>-az-msi-x-001'
+ roleAssignments: [
+ {
+ principalIds: [
+ '<>'
+ ]
+ roleDefinitionIdOrName: 'Reader'
+ }
+ ]
+ }
+}
+```
+
+
+
+
+
+
+via JSON Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "lock": {
+ "value": "CanNotDelete"
+ },
+ "name": {
+ "value": "<>-az-msi-x-001"
+ },
+ "roleAssignments": {
+ "value": [
+ {
+ "principalIds": [
+ "<>"
+ ],
+ "roleDefinitionIdOrName": "Reader"
+ }
+ ]
+ }
+ }
+}
+```
+
+
+
diff --git a/common/attestationpolicy.txt b/common/attestationpolicy.txt
new file mode 100644
index 0000000..fc70e11
--- /dev/null
+++ b/common/attestationpolicy.txt
@@ -0,0 +1,9 @@
+version= 1.0;
+authorizationrules
+{
+ [ type=="x-ms-sgx-is-debuggable", value==false ]
+ && [ type=="x-ms-sgx-product-id", value==4639 ]
+ && [ type=="x-ms-sgx-svn", value>= 2 ]
+ && [ type=="x-ms-sgx-mrsigner", value=="e31c9e505f37a58de09335075fc8591254313eb20bb1a27e5443cc450b6e33e5"]
+ => permit();
+};
\ No newline at end of file
diff --git a/common/common.ps1 b/common/common.ps1
new file mode 100644
index 0000000..7f53154
--- /dev/null
+++ b/common/common.ps1
@@ -0,0 +1,286 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script serves common functions to deploy sample apps and Workload templates.
+
+.DESCRIPTION
+- Executes the individual modules - lighthouse
+
+#>
+
+using namespace System.Collections
+
+param (
+ $parAttendedLogin = $true
+)
+
+#variables to support incremental delay for azure resource validation checks (All time in seconds)
+$varMaxIntervalResourceExistsCheck = 60
+$varIntervalMultiplierResourceExistsCheck = 5
+
+<#
+.Description
+ Login to Azure portal
+#>
+function Enter-Login {
+ Write-Information ">>> Initiating a login" -InformationAction Continue
+ Connect-AzAccount
+}
+
+<#
+.Description
+ Get details of user
+#>
+function Get-SignedInUser {
+
+ $varSignedInUserDetails = Get-AzADUser -SignedIn
+
+ if (!$varSignedInUserDetails) {
+ Write-Information ">>> No logged in user found." -InformationAction Continue
+ }
+ else {
+ return $varSignedInUserDetails.UserPrincipalName
+ }
+
+ return $null
+
+}
+
+<#
+.Description
+ Confirm the user is owner at the root scope
+#>
+function Confirm-UserOwnerPermission {
+ if ($null -ne $varSignedInUser) {
+
+ Write-Information "`n>>> Checking the owner permissions for user: $varSignedInUser at '/' scope" -InformationAction Continue
+ $varRetrieveOwnerPermissions = Get-AzRoleAssignment `
+ -SignInName $varSignedInUser `
+ -Scope "/" `
+ -RoleDefinitionName "Owner"
+
+ if ($varRetrieveOwnerPermissions.RoleDefinitionName -ne "Owner") {
+ Write-Information "Signed in user: $varSignedInUser does not have owner permission to the root '/' scope." -InformationAction Continue
+ return $false
+ }
+ else {
+ Write-Information "Signed in user: $varSignedInUser has owner permissions at the root '/' scope." -InformationAction Continue
+ }
+ return $true
+ }
+ else {
+ Write-Error "Logged in user details are empty." -ErrorAction Stop
+ }
+}
+
+function Set-UserOwnerPermission {
+ Write-Host ">>> Assigning user with Owner permissions."
+
+ # Assign "Owner" role to the signed-in user at the root scope "/"
+ New-AzRoleAssignment `
+ -SignInName $varSignedInUser `
+ -Scope "/" `
+ -RoleDefinitionName "Owner"
+}
+
+function Invoke-UserPermissionsConfirmation {
+ param($parPermissionType)
+ Write-Host "`n>>> Confirming user's permissions. This might trigger an auto log out and require the user to login back in a few times"
+
+ $varWaitTime = 10
+ $varLoopCounter = 0
+
+ while ($varTotalWaitTime -lt $varMaxWaitTimeResourceExistsCheck -and $varUserPermissions -eq $false) {
+ try {
+ # Log out to refresh the session
+ Get-AzContext | Remove-AzContext -Confirm:$false
+ Connect-AzAccount
+
+ # check owner permissions of the user
+ $varUserPermissions = Confirm-UserOwnerPermission
+
+ if ($varUserPermissions -ne $true) {
+ Write-Host ">>> Checking the permissions after waiting for $varWaitTime secs. Please ensure that you are logged into the appropriate tenant and did not log in to a different tenant during the script execution."
+ $varLoopCounter++
+ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter
+ $varTotalWaitTime += $varWaitTime
+ Start-Sleep -Seconds $varWaitTime
+ }
+ }
+ catch {
+ $_.Exception
+ Write-Host ">>> Retrying after waiting for $varWaitTime secs. To stop the retry press Ctrl+C."
+ $varLoopCounter++
+ $varWaitTime = New-IncrementalDelay $varWaitTime $varLoopCounter
+ $varTotalWaitTime += $varWaitTime
+ Start-Sleep -Seconds $varWaitTime
+ }
+ }
+}
+
+<#
+.Description
+ Caclulates and returns the number of seconds to wait
+#>
+function New-IncrementalDelay {
+ param($parDelay, $parDelayIterator)
+ $parDelay = $parDelay + ($parDelayIterator * $varIntervalMultiplierResourceExistsCheck)
+ if ($parDelay -ge $varMaxIntervalResourceExistsCheck) {
+ $parDelay = $varMaxIntervalResourceExistsCheck
+ }
+ return $parDelay
+}
+
+<#
+.Description
+ Confirm parameters
+#>
+function Confirm-Parameters($parParameters) {
+ $varMissingParameters = New-Object Collections.Generic.List[String]
+ $varArrayParameters = @("parAllowedLocations", "parAllowedLocationsForConfidentialComputing", "parPolicyDefinitionReferenceIds")
+ Foreach ($varParameter in $parParameters) {
+ if ($varParameter -in $varArrayParameters -and $varParameters.$varParameter.value.count -eq 0) {
+ if (!$parAttendedLogin) {
+ $varMissingParameters.add($varParameter)
+ }
+ else {
+ [string[]] $varArray = @()
+ $varArray = Read-Host "Please enter the list of $varParameter with a comma between each"
+ if ($varArray[0] -eq "") {
+ Write-Error "$varParameter value not found" -ErrorAction Stop
+ }
+ $varParameters.$varParameter.value = $varArray.Split(',')
+ }
+ }
+ elseif (($null -eq $varParameters.$varParameter.value) -or [string]::IsNullOrEmpty($varParameters.$varParameter.value) -or ($varParameters.$varParameter.value -eq "{}")) {
+ $varParameters.$varParameter.value = $null
+ if (!$parAttendedLogin) {
+ $varMissingParameters.add($varParameter)
+ }
+ else {
+ $varParameters.$varParameter.value = $(Read-Host -prompt "Please provide the value for $varParameter")
+ if ($varParameters.$varParameter.value -eq "") {
+ Write-Error "$varParameter value not found" -ErrorAction Stop
+ }
+ }
+ }
+ elseif ($varParameters.$varParameter.value.count -gt 1) {
+ $varValue = $varParameters.$varParameter.value
+ if ($varValue -is [array]) {
+ foreach ($varElement in $varValue) {
+ $varResult = Confirm-ObjectType($varElement)
+ if ($varResult -eq $false) {
+ $varMissingParameters.add($varParameter)
+ }
+ }
+ }
+ elseif ($varValue -is [object]) {
+ $varResult = Confirm-ObjectType($varValue)
+ if ($varResult -eq $false) {
+ $varMissingParameters.add($varParameter)
+ }
+ }
+ elseif (($null -eq $varValue) -or [string]::IsNullOrEmpty($varValue) -or ($varValue -eq "{}")) {
+ $varParameters.$varParameter.value = $null
+ return $false
+ }
+ }
+ }
+ if ($varMissingParameters.count -gt 0) {
+ Write-Error "Following parameters are missing : $varMissingParameters" -ErrorAction Stop
+ }
+
+ $varTenantId = (Get-AzTenant).Id
+
+ if ($varTenantId -eq $varParameters.parConfidentialVirtualMachineManagementTenantId.value ) {
+ Write-Error "The value of parameter named parConfidentialVirtualMachineManagementTenantId should not be the same as the tenant id $varTenantId where the ConfidentialVirtualMachine script is being deployed. Please use different tenant id for the parConfidentialVirtualMachineManagementTenantId." -ErrorAction Stop
+ }
+}
+
+<#
+.Description
+ Checks the required Object type parameters are passed based on the deployment.
+#>
+function Confirm-ObjectType($parParameter) {
+ if (($null -eq $parParameter)) {
+ return $false
+ }
+
+ $varMembers = $parParameter.PSObject.Properties | Select-Object Name, Value
+ foreach ($varMember in $varMembers) {
+ if (($null -eq $varMember.value) -or [string]::IsNullOrEmpty($varMember.value) -or ($varMember.value -eq "")) {
+ return $false
+ }
+ }
+
+ return $true
+}
+
+
+<#
+.Description
+ Register Microsoft.Compute namespace.
+#>
+function Register-Compute {
+ Write-Information ">>> Registering EncryptionAtHost feature. This may take up to 30 minutes." -InformationAction Continue
+ Register-AzProviderFeature -FeatureName "EncryptionAtHost" -ProviderNamespace "Microsoft.Compute"
+ while ((Get-AzProviderFeature -FeatureName "EncryptionAtHost" -ProviderNamespace "Microsoft.Compute").RegistrationState -ne "Registered") {
+ Write-Information ">>> Waiting on feature registration to complete, checking again in $varRetryWaitTime seconds." -InformationAction Continue
+ Start-Sleep -Seconds $varRetryWaitTime
+ }
+ Write-Information ">>> EncryptionAtHost feature registered." -InformationAction Continue
+}
+
+<#
+.Description
+ Register resource provider.
+#>
+function Register-ResourceProvider {
+ param($varProviderNamespace)
+
+ $varResourceProvider = $null
+ $varLoopCounter = 0
+
+ $varJob = Register-AzResourceProvider -ProviderNamespace $varProviderNamespace -AsJob
+ $varJob | Wait-Job > $null
+
+ $varResourceProvider = Get-AzResourceProvider -ProviderNamespace $varProviderNamespace
+ while ($null -eq $varResourceProvider -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) {
+ Start-Sleep -Seconds $varRetryWaitTimeTransientErrorRetry
+ $varResourceProvider = Get-AzResourceProvider -ProviderNamespace $varProviderNamespace
+ $varLoopCounter++
+ }
+}
+
+<#
+.Description
+ Checks Prerequisites for the deployment.
+#>
+function Confirm-Prerequisites {
+ param(
+ [int]$parConfirmAZResourceGraphVersion = 0
+ )
+
+ Write-Information ">>> Checking Prerequisites for the deployment" -InformationAction Continue
+ $varConfirmPrerequisites = '..\..\..\common\confirm-prerequisites.ps1'
+ & $varConfirmPrerequisites -parAttendedLogin $parAttendedLogin -parConfirmAZResourceGraphVersion $parConfirmAZResourceGraphVersion -ErrorAction Stop
+ Write-Information ">>> Checking Prerequisites is complete." -InformationAction Continue
+ return
+}
+
+<#
+.Description
+ Load all the Do not retry error codes from the json file in a hashtable
+#>
+function Get-DonotRetryErrorCodes {
+ param ($parFilePath)
+
+ $varList = New-Object Collections.Generic.List[String]
+ $varFile = Get-Content -Path $parFilePath | ConvertFrom-Json
+ $varFile.errorCodes | ForEach-Object {
+ $varList.add($_.code)
+ }
+
+ return $varList
+}
diff --git a/common/confirm-prerequisites.ps1 b/common/confirm-prerequisites.ps1
new file mode 100644
index 0000000..c2165d5
--- /dev/null
+++ b/common/confirm-prerequisites.ps1
@@ -0,0 +1,293 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script executes the below list of prerequisite checks to confirm before execution.
+
+- Verify PowerShell Verion
+- Verify Azure PowerShell version
+- Verify Azure CLI version
+- Update Bicep version
+- Check the user executing has the owner permission on the root ("/") scope of the tenant and assign root ("/") permission if the user is missing the same
+
+AUTHOR/S: Cloud for Sovereignty
+#>
+
+param ()
+
+$varSignedInUser = $null;
+
+#reference to individual scripts
+. "..\..\..\common\common.ps1"
+
+function Confirm-PowerShellVersion {
+ <#
+
+ .SYNOPSIS
+ This function checks the current version of PowerShell and prompts the user to install the latest version if the current version is not compatible with the script.
+ .EXAMPLE
+ Confirm-PowerShellVersion
+ .EXAMPLE
+ Confirm-PowerShellVersion -parMajorVersion 7 -parMinorVersion 1
+ .PARAMETER parMajorVersion
+ The major version of PowerShell to check for
+ .PARAMETER parMinorVersion
+ The minor version of PowerShell to check for
+
+ #>
+ param(
+ [Parameter(Mandatory = $false)]
+ [int]$parMajorVersion = 7,
+
+ [Parameter(Mandatory = $false)]
+ [int]$parMinorVersion = 1
+ )
+ $varVersion = $PSVersionTable.PSVersion
+ Write-Information "`n>>> Checking if the current version of PowerShell $varVersion is compatible with the script- " -InformationAction Continue
+
+ if ($varVersion.Major -eq $parMajorVersion -and $varVersion.Minor -ge $parMinorVersion) {
+ Write-Information "The installed version of PowerShell is compatible with the script." -InformationAction Continue
+ return $true
+ }
+ else {
+ Write-Warning "The installed version of PowerShell is not compatible with the script. Please upgrade to the latest version." -WarningAction Continue
+ $varConfirmLatestPSInstalled = $(Read-Host -prompt "Do you want to install the latest version of PowerShell (Y/N)?")
+ if ($varConfirmLatestPSInstalled -eq 'Y' -or $varConfirmLatestPSInstalled -eq 'y') {
+ Write-Information "Installing the latest version of PowerShell" -InformationAction Continue
+ if (Get-Command winget -errorAction SilentlyContinue) {
+ winget install --id Microsoft.Powershell --source winget *> $null
+ Write-Information "To continue with the next steps, please switch to the latest version of installed PowerShell. Exiting the script." -InformationAction Continue
+ Exit
+ }
+ else {
+ Start-Process "https://aka.ms/install-powershell"
+ }
+ return $true
+ }
+ return $false
+ }
+}
+function Confirm-AZPSVersion {
+ <#
+
+ .SYNOPSIS
+ This function checks the current version of Azure PowerShell module and prompts the user to install the latest version if the current version is not compatible with the script.
+ .EXAMPLE
+ Confirm-AZPSVersion
+ .EXAMPLE
+ Confirm-AZPSVersion -parMajorVersion 9
+ .PARAMETER parMajorVersion
+ The major version of Azure PowerShell module to check for
+
+ #>
+ param(
+ [Parameter(Mandatory = $false)]
+ [int]$parMajorVersion = 9
+ )
+ try {
+ $varAzPsVersion = (Get-InstalledModule -Name Az).Version
+ }
+ catch {
+ Write-Information "Installing the latest version of Azure AZ PowerShell module. This could take up to 5 minutes. You will be prompted with next step when this installation completes." -InformationAction Continue
+ Install-Module -Name Az -AllowClobber -Force
+ }
+ $varAzPsVersion = (Get-InstalledModule -Name Az).Version
+ $varCompatibleVersionInstalled = [Version]$varAzPsVersion -ge [Version]"$parMajorVersion.0.0"
+ if ($varCompatibleVersionInstalled) {
+ Write-Information "The installed version of Azure AZ PowerShell module is compatible with the script." -InformationAction Continue
+ }
+ else {
+ Write-Warning "The installed version of Azure AZ PowerShell module ($varAzPsVersion) is not compatible with the script. Please upgrade to the latest version." -WarningAction Continue
+ $confirmLatestAzureAZModuleInstalled = $(Read-Host -prompt "Do you want to install the latest version of Azure AZ PowerShell module (Y/N)?")
+ if ($confirmLatestAzureAZModuleInstalled -eq 'Y' -or $confirmLatestAzureAZModuleInstalled -eq 'y') {
+ Write-Information "Installing the latest version of Azure AZ PowerShell module. This could take up to 5 minutes. You will be prompted with next step when this installation completes." -InformationAction Continue
+ Install-Module -Name Az -AllowClobber -Force
+ }
+ }
+ return $varCompatibleVersionInstalled
+}
+
+function Confirm-AZCLIVersion {
+ <#
+ .SYNOPSIS
+ This function checks the current version of Azure CLI and prompts the user to install the latest version if the current version is not compatible with the script.
+ .EXAMPLE
+ Confirm-AZCLIVersion
+ .EXAMPLE
+ Confirm-AZCLIVersion -parMajorVersion 2 -parMinorVersion 40
+ .PARAMETER parMajorVersion
+ The major version of Azure CLI to check for
+ .PARAMETER parMinorVersion
+ The minor version of Azure CLI to check for
+ #>
+ param(
+ [Parameter(Mandatory = $false)]
+ [int]$parMajorVersion = 2,
+
+ [Parameter(Mandatory = $false)]
+ [int]$parMinorVersion = 40
+ )
+ Write-Information "`n>>> Check the current version of azure cli installed - " -InformationAction Continue
+ $varHasCompatibleVersion = $false
+ if (Get-Command "az" -errorAction SilentlyContinue) {
+ $azversion = ((az version -o tsv) -split "\t")[0] -split "\."
+ $varCompatibleVersionInstalled = $azversion[0] -eq $parMajorVersion -and $azversion[1] -ge $parMinorVersion
+ if ($varCompatibleVersionInstalled) {
+ $varHasCompatibleVersion = $true
+ Write-Information "The installed version of Azure CLI is compatible with the script." -InformationAction Continue
+ }
+ else {
+ Write-Warning "The installed version of Azure CLI is not compatible with the script. Please upgrade to the latest version." -WarningAction Continue
+ }
+ }
+
+ if ($varHasCompatibleVersion -eq $false) {
+ $confirmLatestAzureCLIInstalled = $(Read-Host -prompt "Do you want to install the latest version of Azure CLI (Y/N)?")
+ if ($confirmLatestAzureCLIInstalled -eq 'Y' -or $confirmLatestAzureCLIInstalled -eq 'y') {
+ Write-Information "Installing the latest version of Azure CLI" -InformationAction Continue
+ Start-Process "https://learn.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest"
+ return $true
+ }
+ return $false
+ }
+ return $true
+}
+
+function Confirm-BicepVersion {
+ <#
+ .SYNOPSIS
+ This function checks the current version of Azure Bicep and prompts the user to install the latest version
+ .EXAMPLE
+ Confirm-BicepVersion
+ #>
+ Write-Information "`n>>> Check the current version of Azure Bicep installed - " -InformationAction Continue
+
+ $varHasCompatibleVersion = $false
+ $varCurrentBicepVersion = ""
+ $varCurrentBicepVersion = ((az bicep version) -split " ")[3]
+
+ if (($varCurrentBicepVersion -ne "") -and ($null -ne $varCurrentBicepVersion)) {
+ ##when az bicep version command is run, platform already prints the latest version of the Azure Bicep, so no need to print it again.
+ Write-Information "The installed version of Azure Bicep: $varCurrentBicepVersion" -InformationAction Continue
+
+ $latestAvailableBicepVersion = ((az bicep list-versions | ConvertFrom-Json)[0]).Replace("v", "")
+ if ($varCurrentBicepVersion -eq $latestAvailableBicepVersion) {
+ $varHasCompatibleVersion = $true
+ Write-Information "The installed version of Azure Bicep is latest." -InformationAction Continue
+ }
+ else {
+ Write-Warning "The installed version of Azure Bicep is not latest. Please upgrade to the latest version $latestAvailableBicepVersion to continue." -WarningAction Continue
+ }
+ }
+ else {
+ Write-Information "Azure Bicep is not installed currently." -InformationAction Continue
+ }
+
+ if ($varHasCompatibleVersion -eq $false) {
+ $varConfirmLatestAzureBicep = $(Read-Host -prompt "Do you want to install the latest version of Azure Bicep? (Y/N)?")
+ if ($varConfirmLatestAzureBicep -eq 'Y' -or $varConfirmLatestAzureBicep -eq 'y') {
+ Write-Information "Installing the latest version of Azure Bicep" -InformationAction Continue
+ if ($varCurrentBicepVersion -eq "") {
+ az bicep install
+ }
+ else {
+ az bicep upgrade
+ }
+ }
+ else {
+ Write-Warning "The installed version of Azure Bicep is not compatible with the script. Please upgrade to the latest version." -WarningAction Continue
+ return $false
+ }
+ }
+ return $true
+}
+function Confirm-AZResourceGraphVersion {
+ <#
+ .SYNOPSIS
+ This function checks the current version of Az.ResourceGraph module and prompts the user to install the latest version if the current version is not compatible with the script.
+ #>
+ param()
+ $varRgVersion = $null
+ try {
+ $varRgVersion = (Get-InstalledModule -Name Az.ResourceGraph).Version
+ if ($null -eq $varRgVersion) {
+ Install-Module -Name Az.ResourceGraph -AllowClobber -Force
+ $varRgVersion = (Get-InstalledModule -Name Az.ResourceGraph).Version
+ }
+ }
+ catch {
+ Write-Information "Installing the latest version of Azure Az.ResourceGraph PowerShell module. This could take up to 5 minutes. You will be prompted with next step when this installation completes." -InformationAction Continue
+ Install-Module -Name Az.ResourceGraph -AllowClobber -Force
+ $varRgVersion = (Get-InstalledModule -Name Az.ResourceGraph).Version
+ }
+
+ Write-Information "The version of Az.ResourceGraph module is $varRgVersion" -InformationAction Continue
+ return
+}
+
+<#
+ .SYNOPSIS
+ This function Confirm the pre-requisites to be executed
+ .EXAMPLE
+ Confirm-PreRequisites
+#>
+function Confirm-PreRequisites {
+ param(
+ [bool]$parConfirmPsVersion = $true,
+ [bool]$parConfirmAzPsVersion = $true,
+ [bool]$parConfirmAzCliVersion = $true,
+ [bool]$parConfirmBicepVersion = $true,
+ [bool]$parConfirmAzResGraphVersion = $false
+ )
+
+ if ($parConfirmPsVersion) {
+ $varPsVerCompatible = Confirm-PowerShellVersion
+ }
+
+ if ($parConfirmAzPsVersion) {
+ $varAzPsVerCompatible = Confirm-AZPSVersion
+ }
+
+ if ($parConfirmAzCliVersion) {
+ $varAzCliVerCompatible = Confirm-AZCLIVersion
+ }
+
+ if ($parConfirmBicepVersion) {
+ $varBicepVerCcompatible = Confirm-BicepVersion
+ }
+
+ if ($parConfirmAzResGraphVersion) {
+ Confirm-AZResourceGraphVersion
+ }
+
+ if ($varPsVerCompatible -eq $false -or $varAzPsVerCompatible -eq $false -or $varAzCliVerCompatible -eq $false -or $varBicepVerCcompatible -eq $false) {
+ Write-Error "Please install the latest version of PowerShell, Azure PowerShell module, Azure CLI and bicep and try again." -ErrorAction Stop
+ }
+
+ $varSignedInUser = Get-SignedInUser
+
+ # if user is not signed in trigger login
+ if ($null -eq $varSignedInUser) {
+ Enter-Login
+ $varSignedInUser = Get-SignedInUser
+ }
+
+ # check owner permissions of the user
+ $varUserPermissions = Confirm-UserOwnerPermission
+
+ # if user does not have owner permissions.
+ if ($varUserPermissions -ne $true) {
+ Write-Error "`n>>> Signed in user: $varSignedInUser doesn't have the necessary permissions." -ErrorAction Stop
+ }
+
+ Write-Information "`n>>> Signed in user: $varSignedInUser has the necessary permissions." -InformationAction Continue
+ Write-Information "`n>>> Please go ahead and execute - deployment script" -InformationAction Continue
+ Write-Information "`n>>> Please remember to review the parameters.json file before deployment." -InformationAction Continue
+}
+
+try {
+ Confirm-PreRequisites -parConfirmAzResGraphVersion $parConfirmAZResourceGraphVersion
+}
+catch {
+ Write-Error $_
+}
diff --git a/common/const/doNotRetryErrorCodes.json b/common/const/doNotRetryErrorCodes.json
new file mode 100644
index 0000000..8a9370d
--- /dev/null
+++ b/common/const/doNotRetryErrorCodes.json
@@ -0,0 +1,21 @@
+{
+ "description": "This file contains all the error codes for while the retry logic would not be triggered",
+ "errorCodes": [
+ {
+ "code": "RequestDisallowedByPolicy",
+ "errorDescription": "A resource deployment was disallowed by policy"
+ },
+ {
+ "code": "MissingSubscriptionRegistration",
+ "errorDescription": "The subscription must be registered to use the namespace."
+ },
+ {
+ "code": "ReferencedResourceNotProvisioned",
+ "errorDescription": "Cannot proceed with operation because the resource is not in Succeeded state."
+ },
+ {
+ "code": "UserNotAuthorized",
+ "errorDescription": "User is not authorized to create a particular resource/subscription"
+ }
+ ]
+}
diff --git a/common/customRoles/customRoleAssignment.bicep b/common/customRoles/customRoleAssignment.bicep
new file mode 100644
index 0000000..6b43804
--- /dev/null
+++ b/common/customRoles/customRoleAssignment.bicep
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the custom role assignment.
+DESCRIPTION: This module will create a deployment at the management group level which will create the custom role assignment.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+@description('Role Definition Id')
+param parRoleDefinitionId string
+
+@description('Principal Id of resource for role assignment')
+param parPrincipalId string
+
+@description('Service principal type')
+@allowed([
+ 'Device'
+ 'ForeignGroup'
+ 'Group'
+ 'ServicePrincipal'
+ 'User'
+])
+param parPrincipalType string
+
+@description('A GUID representing the role assignment name. Default: guid(managementGroup().name, parRoleDefinitionId, parPrincipalId)')
+var roleAssignmentName = guid(managementGroup().name, parRoleDefinitionId, parPrincipalId)
+
+resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: managementGroup()
+ name: roleAssignmentName
+ properties: {
+ roleDefinitionId: parRoleDefinitionId
+ principalId: parPrincipalId
+ principalType:parPrincipalType
+ }
+ }
diff --git a/common/customRoles/customRoleDefinition.bicep b/common/customRoles/customRoleDefinition.bicep
new file mode 100644
index 0000000..f90b982
--- /dev/null
+++ b/common/customRoles/customRoleDefinition.bicep
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the custom role definition.
+DESCRIPTION: This module will create a deployment at the management group level which will create the custom role definition.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+@description('Array of actions for the roleDefinition')
+param actions array = []
+
+@description('Array of notActions for the roleDefinition')
+param notActions array = []
+
+@description('Friendly name of the role definition')
+param roleName string
+
+@description('Detailed description of the role definition')
+param roleDescription string
+
+var roleDefName = guid(managementGroup().id,roleName)
+
+resource roleDef 'Microsoft.Authorization/roleDefinitions@2022-04-01' = {
+ name: roleDefName
+ properties: {
+ roleName: roleName
+ description: roleDescription
+ type: 'customRole'
+ permissions: [
+ {
+ actions: actions
+ notActions: notActions
+ }
+ ]
+ assignableScopes: [
+ managementGroup().id
+ ]
+ }
+}
+
+output outRoleDefinitionId string = roleDef.id
diff --git a/common/modules/module.peering.bicep b/common/modules/module.peering.bicep
new file mode 100644
index 0000000..96592c0
--- /dev/null
+++ b/common/modules/module.peering.bicep
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the network peering between two virtual networks.
+DESCRIPTION: This module will create a deployment which will create the network peering between two virtual networks.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('The VNet on which this peering is created.')
+param parHomeNetworkName string
+
+@description('The VNet that this peering targets.')
+param parRemoteNetworkId string
+
+@description('Whether to use the remote virtual network\'s gateway or Route Server (typically only true if this is a spoke network).')
+param parUseRemoteGateways bool
+
+@description('Whether to allow remote network to utilize gateway links in this home network (typically only true if this is a hub network).')
+param parAllowGatewayTransit bool
+
+// Existing VNet resource in which this peering is deployed
+resource resHomeVNet 'Microsoft.Network/virtualNetworks@2022-07-01' existing = {
+ name: parHomeNetworkName
+}
+
+// Peering to the target vnet
+resource resPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2022-07-01' = {
+ name: 'peering-${uniqueString(resourceGroup().id, subscription().id, parRemoteNetworkId)}'
+ parent: resHomeVNet
+ properties: {
+ remoteVirtualNetwork: {
+ id: parRemoteNetworkId
+ }
+ allowVirtualNetworkAccess: true
+ allowForwardedTraffic: true
+ allowGatewayTransit: parAllowGatewayTransit
+ useRemoteGateways: parUseRemoteGateways
+ doNotVerifyRemoteGateways: false
+ peeringState: 'Connected'
+ }
+}
diff --git a/common/modules/module.privatedns.bicep b/common/modules/module.privatedns.bicep
new file mode 100644
index 0000000..ce1f260
--- /dev/null
+++ b/common/modules/module.privatedns.bicep
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the private DNS.
+DESCRIPTION: This module will create a deployment which will create the Private DNS zones and Link Attestation Provider DNS zone to hub network and ContosoHR network.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Resource ID of the hub network in which the Private DNS zones will be deployed.')
+param parHubNetworkId string
+
+@description('Resource ID of the ContosoHR VNet from ContosoHR.Common, to which the Private DNS zones will be linked.')
+param parSpokeNetworkId string
+
+// Private DNS Zones for Attestation Provider and SQL Server services.
+// See reference listing at https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns
+var varAttestationProviderZoneName = 'privatelink.attest.azure.net'
+resource resAttestationPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
+ name: varAttestationProviderZoneName
+ location: 'global'
+}
+
+var varSqlServerZoneName = 'privatelink${environment().suffixes.sqlServerHostname}'
+resource resSqlServerPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
+ name: varSqlServerZoneName
+ location: 'global'
+}
+
+// Link Attestation Provider DNS zone to hub network
+resource resAttestationDnsZoneHubVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
+ name: 'link-${uniqueString(resAttestationPrivateDnsZone.id, parHubNetworkId)}'
+ parent: resAttestationPrivateDnsZone
+ location: 'global'
+ properties: {
+ virtualNetwork: {
+ id: parHubNetworkId
+ }
+ registrationEnabled: false
+ }
+}
+
+// Link Attestation Provider DNS zone to ContosoHR network
+resource resAttestationDnsZoneSpokeVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
+ name: 'link-${uniqueString(resAttestationPrivateDnsZone.id, parSpokeNetworkId)}'
+ parent: resAttestationPrivateDnsZone
+ location: 'global'
+ properties: {
+ virtualNetwork: {
+ id: parSpokeNetworkId
+ }
+ registrationEnabled: false
+ }
+}
+
+// Link SQL Server DNS zone to ContosoHR network
+// Note that this DNS zone is NOT linked to the Hub network in this module
+// -- because it is already set up and linked there by the installer by default
+resource resSqlServerDnsZoneSpokeVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
+ name: 'link-${uniqueString(resSqlServerPrivateDnsZone.id, parSpokeNetworkId)}'
+ parent: resSqlServerPrivateDnsZone
+ location: 'global'
+ properties: {
+ virtualNetwork: {
+ id: parSpokeNetworkId
+ }
+ registrationEnabled: false
+ }
+}
+
+output outAttestationDnsZoneId string = resAttestationPrivateDnsZone.id
+output outSqlServerDnsZoneId string = resSqlServerPrivateDnsZone.id
diff --git a/common/resourceGroup/resourceGroup.bicep b/common/resourceGroup/resourceGroup.bicep
new file mode 100644
index 0000000..78d27b1
--- /dev/null
+++ b/common/resourceGroup/resourceGroup.bicep
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the resource group.
+DESCRIPTION: This module will create a deployment at subscription level which will create the resource group and add the customer usage attribution (PID) to Subscription deployments.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'subscription'
+
+metadata name = 'ALZ Bicep - Resource Group creation module'
+metadata description = 'Module used to create Resource Groups'
+
+@sys.description('Azure Region where Resource Group will be created.')
+param parLocation string
+
+@sys.description('Name of Resource Group to be created.')
+param parResourceGroupName string
+
+@sys.description('Tags you would like to be applied to all resources in this module.')
+param parTags object = {}
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry.')
+param parTelemetryOptOut bool = false
+
+// Customer Usage Attribution Id
+var varCuaid = 'b6718c54-b49e-4748-a466-88e3d7c789c8'
+
+resource resResourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+ location: parLocation
+ name: parResourceGroupName
+ tags: parTags
+}
+
+module modCustomerUsageAttribution '../CRML/customerUsageAttribution/cuaIdSubscription.bicep' = if (!parTelemetryOptOut) {
+ name: 'pid-${varCuaid}-${uniqueString(subscription().subscriptionId, parResourceGroupName)}'
+ params: {}
+}
+
+output outResourceGroupName string = resResourceGroup.name
+output outResourceGroupId string = resResourceGroup.id
diff --git a/common/roleAssignments/README.md b/common/roleAssignments/README.md
new file mode 100644
index 0000000..d12b46b
--- /dev/null
+++ b/common/roleAssignments/README.md
@@ -0,0 +1,185 @@
+# Module: Role Assignments for Management Groups & Subscriptions
+
+This module provides role assignment capabilities across Management Group & Subscription scopes. Role assignments are part of [Identity and Access Management (IAM)](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/enterprise-scale/identity-and-access-management), which is one of the critical design areas in Enterprise-Scale Architecture. The role assignments can be performed for:
+
+- Managed Identities (System and User Assigned)
+- Service Principals
+- Security Groups
+
+This module contains 4 Bicep templates, you may optionally choose one of these modules to deploy depending on which scope you want to assign roles from broad to narrow; management group to subscription:
+
+| Template | Description | Deployment Scope |
+| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
+| roleAssignmentManagementGroup.bicep | Performs role assignment on one management group | Management Group |
+| roleAssignmentManagementGroupMany.bicep | Performs role assignment on one or more management groups. This template uses `roleAssignmentManagementGroup.bicep` for the deployments. | Management Group |
+| roleAssignmentSubscription.bicep | Performs role assignment on one subscription | Subscription |
+| roleAssignmentSubscriptionMany.bicep | Performs role assignment on one or more subscriptions. This template uses `roleAssignmentSubscription.bicep` for the deployments. | Management Group |
+| roleAssignmentResourceGroup.bicep | Performs role assignment on one resource group | Resource Group |
+| roleAssignmentResourceGroupMany.bicep | Performs role assignment on one or more resource groups. This template uses `roleAssignmentResourceGroup.bicep` for the deployments. | Management Group |
+
+## Parameters
+
+The module requires the following required input parameters.
+
+All templates require an input for `parAssigneeObjectId` and this value is dependent on the Service Principal type. Azure CLI and PowerShell commands can be executed to identify the correct `object id`. Examples:
+
+### Azure CLI - Find Object ID
+
+```bash
+# Identify Object Id for User Assigned / System Assigned Managed Identity
+# Example: az identity show --resource-group rgManagedIdentities --name alz-managed-identity --query 'principalId'
+az identity show --resource-group --name --query 'principalId'
+
+# Identify Object Id for Service Principal (App Registration)
+# Require read permission to query Azure Active Directory
+# Example: az ad sp show --id c705dc53-7c95-42bc-b1d5-75e172571370 --query id
+az ad sp show --id --query id
+
+# Identify Object Id for Service Principal (App Registration)
+# Require read permission to query Azure Active Directory
+# Beware of duplicates, since app registation names are not unique.
+# Example: az ad sp list --filter "displayName eq ''" --query '[].{name:appDisplayName, objectId:id}'
+az ad sp list --filter "displayName eq ''" --query '[].{name:appDisplayName, objectId:id}'
+
+# Identify Object Id for Security Group
+# Require read permission to query Azure Active Directory
+# Example: az ad group show --group SG_ALZ_SECURITY --query id
+az ad group show --group --query id
+```
+
+### PowerShell - Find Object ID
+
+```powershell
+# Identify Object Id for User Assigned / System Assigned Managed Identity
+# Example: (Get-AzADServicePrincipal -DisplayName 'alz-managed-identity').Id
+(Get-AzADServicePrincipal -DisplayName '').Id
+
+# Identify Object Id for Service Principal (App Registration)
+# Require read permission to query Azure Active Directory
+# Example: (Get-AzADServicePrincipal -DisplayName 'Azure Landing Zone SPN').Id
+(Get-AzADServicePrincipal -DisplayName '').Id
+
+# Identify Object Id for Security Group
+# Require read permission to query Azure Active Directory
+# Example: Get-AzureADGroup -SearchString 'SG_ALZ_SECURITY'
+Connect-AzureAD
+(Get-AzureADGroup -SearchString '').ObjectId
+```
+
+### roleAssignmentManagementGroup.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentManagementGroup.bicep.md)
+
+### roleAssignmentManagementGroupMany.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentManagementGroupMany.bicep.md)
+
+### roleAssignmentSubscription.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentSubscription.bicep.md)
+
+### roleAssignmentSubscriptionMany.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentSubscriptionMany.bicep.md)
+
+### roleAssignmentResourceGroup.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentResourceGroup.bicep.md)
+
+### roleAssignmentResourceGroupMany.bicep
+
+- [Link to Parameters](generateddocs/roleAssignmentResourceGroupMany.bicep.md)
+
+## Outputs
+
+*This module does not produce any outputs.*
+
+## Deployment
+
+In this example, the built-in Reader role will be assigned to a Service Principal account at the `alz-platform` management group scope. The inputs for this module are defined in `parameters/roleAssignmentManagementGroup.*.parameters.all.json`.
+
+> For the examples below we assume you have downloaded or cloned the Git repo as-is and are in the root of the repository as your selected directory in your terminal of choice.
+
+### Azure CLI
+
+```bash
+# For Azure global regions
+
+dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
+NAME="alz-RoleAssignmentsDeployment-${dateYMD}"
+LOCATION="eastus"
+MGID="alz"
+TEMPLATEFILE="infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep"
+PARAMETERS="@infra-as-code/bicep/modules/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json"
+
+New-AzManagementGroupDeployment --name ${NAME:0:63} --location $LOCATION --management-group-id $MGID --template-file $TEMPLATEFILE --parameters $PARAMETERS
+```
+OR
+```bash
+# For Azure China regions
+
+dateYMD=$(date +%Y%m%dT%H%M%S%NZ)
+NAME="alz-RoleAssignmentsDeployment-${dateYMD}"
+LOCATION="chinaeast2"
+MGID="alz"
+TEMPLATEFILE="infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep"
+PARAMETERS="@infra-as-code/bicep/modules/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json"
+
+New-AzManagementGroupDeployment --name ${NAME:0:63} --location $LOCATION --management-group-id $MGID --template-file $TEMPLATEFILE --parameters $PARAMETERS
+```
+
+### PowerShell
+
+```powershell
+# For Azure global regions
+
+$inputObject = @{
+ DeploymentName = 'alz-RoleAssignmentsDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
+ Location = 'eastus'
+ ManagementGroupId = 'alz'
+ TemplateFile = "infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep"
+ TemplateParameterFile = 'infra-as-code/bicep/modules/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json'
+}
+
+New-AzManagementGroupDeployment @inputObject
+```
+OR
+```powershell
+# For Azure China regions
+
+$inputObject = @{
+ DeploymentName = 'alz-RoleAssignmentsDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63])
+ Location = 'chinaeast2'
+ ManagementGroupId = 'alz'
+ TemplateFile = "infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep"
+ TemplateParameterFile = 'infra-as-code/bicep/modules/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json'
+}
+
+New-AzManagementGroupDeployment @inputObject
+```
+
+## Bicep Visualizer
+
+### Single Management Group Role Assignment
+
+![Bicep Visualizer - Single Management Group Role Assignment](media/bicepVisualizerMg.png "Bicep Visualizer - Single Management Group Role Assignment")
+
+### Many Management Group Role Assignments
+
+![Bicep Visualizer - Many Management Group Role Assignments](media/bicepVisualizerMgMany.png "Bicep Visualizer - Many Management Group Role Assignments")
+
+### Single Subscription Role Assignment
+
+![Bicep Visualizer - Single Subscription Role Assignment](media/bicepVisualizerSub.png "Bicep Visualizer - Single Subscription Role Assignment")
+
+### Many Subscription Role Assignments
+
+![Bicep Visualizer - Many Subscription Role Assignments](media/bicepVisualizerSubMany.png "Bicep Visualizer - Many Subscription Role Assignments")
+
+### Single Resource Group Role Assignment
+
+![Bicep Visualizer - Single Resource Group Role Assignment](media/bicepVisualizerSub.png "Bicep Visualizer - Single Resource Group Role Assignment")
+
+### Many Resource Group Role Assignments
+
+![Bicep Visualizer - Many Resource Group Role Assignments](media/bicepVisualizerSubMany.png "Bicep Visualizer - Many Resource Group Role Assignments")
diff --git a/common/roleAssignments/bicepconfig.json b/common/roleAssignments/bicepconfig.json
new file mode 100644
index 0000000..dceea76
--- /dev/null
+++ b/common/roleAssignments/bicepconfig.json
@@ -0,0 +1,88 @@
+{
+ "analyzers": {
+ "core": {
+ "enabled": true,
+ "verbose": false,
+ "rules": {
+ "adminusername-should-not-be-literal": {
+ "level": "error"
+ },
+ "no-hardcoded-env-urls": {
+ "level": "error"
+ },
+ "no-unnecessary-dependson": {
+ "level": "error"
+ },
+ "no-unused-params": {
+ "level": "error"
+ },
+ "no-unused-vars": {
+ "level": "error"
+ },
+ "outputs-should-not-contain-secrets": {
+ "level": "error"
+ },
+ "prefer-interpolation": {
+ "level": "error"
+ },
+ "secure-parameter-default": {
+ "level": "error"
+ },
+ "simplify-interpolation": {
+ "level": "error"
+ },
+ "protect-commandtoexecute-secrets": {
+ "level": "error"
+ },
+ "use-stable-vm-image": {
+ "level": "error"
+ },
+ "explicit-values-for-loc-params": {
+ "level": "error"
+ },
+ "no-hardcoded-location": {
+ "level": "error"
+ },
+ "no-loc-expr-outside-params": {
+ "level": "error"
+ },
+ "max-outputs": {
+ "level": "error"
+ },
+ "max-params": {
+ "level": "error"
+ },
+ "max-resources": {
+ "level": "error"
+ },
+ "max-variables": {
+ "level": "error"
+ },
+ "artifacts-parameters":{
+ "level": "error"
+ },
+ "no-unused-existing-resources":{
+ "level": "error"
+ },
+ "prefer-unquoted-property-names":{
+ "level": "error"
+ },
+ "secure-params-in-nested-deploy":{
+ "level": "error"
+ },
+ "secure-secrets-in-params":{
+ "level": "error"
+ },
+ "use-recent-api-versions":{
+ "level": "error"
+ },
+ "use-resource-id-functions":{
+ "level": "error"
+ },
+ "use-stable-resource-identifiers":{
+ "level": "error"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/generateddocs/roleAssignmentManagementGroup.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentManagementGroup.bicep.md
new file mode 100644
index 0000000..73f4a41
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentManagementGroup.bicep.md
@@ -0,0 +1,80 @@
+# ALZ Bicep - Role Assignment to a Management Group
+
+Module used to create a role assignment to Management Group
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parRoleAssignmentNameGuid | No | A GUID representing the role assignment name.
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry.
+
+### parRoleAssignmentNameGuid
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A GUID representing the role assignment name.
+
+- Default value: `[guid(managementGroup().name, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]`
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry.
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.json"
+ },
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "[guid(managementGroup().name, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]"
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/generateddocs/roleAssignmentManagementGroupMany.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentManagementGroupMany.bicep.md
new file mode 100644
index 0000000..018085f
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentManagementGroupMany.bicep.md
@@ -0,0 +1,78 @@
+# ALZ Bicep - Role Assignment to Management Groups
+
+Module used create an RBAC Role Assignment to multiple Management Groups
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parManagementGroupIds | No | A list of management group scopes that will be used for role assignment (i.e. [alz-platform-connectivity, alz-platform-identity]).
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry
+
+### parManagementGroupIds
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A list of management group scopes that will be used for role assignment (i.e. [alz-platform-connectivity, alz-platform-identity]).
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroupMany.json"
+ },
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": []
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/generateddocs/roleAssignmentResourceGroup.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentResourceGroup.bicep.md
new file mode 100644
index 0000000..bf3bb3d
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentResourceGroup.bicep.md
@@ -0,0 +1,80 @@
+# ALZ Bicep - Role Assignment to a Resource Group
+
+Module used to create a Role Assignment to a Resource Group
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parRoleAssignmentNameGuid | No | A GUID representing the role assignment name.
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry.
+
+### parRoleAssignmentNameGuid
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A GUID representing the role assignment name.
+
+- Default value: `[guid(resourceGroup().id, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]`
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry.
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentResourceGroup.json"
+ },
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "[guid(resourceGroup().id, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]"
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/generateddocs/roleAssignmentResourceGroupMany.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentResourceGroupMany.bicep.md
new file mode 100644
index 0000000..6c21f38
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentResourceGroupMany.bicep.md
@@ -0,0 +1,78 @@
+# ALZ Bicep - Role Assignment to Resource Groups
+
+Module used to create a Role Assignment to multiple Resource Groups
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parResourceGroupIds | No | A list of Resource Groups that will be used for role assignment in the format of subscriptionId/resourceGroupName (i.e. a1fe8a74-e0ac-478b-97ea-24a27958961b/rg01).
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry
+
+### parResourceGroupIds
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A list of Resource Groups that will be used for role assignment in the format of subscriptionId/resourceGroupName (i.e. a1fe8a74-e0ac-478b-97ea-24a27958961b/rg01).
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentResourceGroupMany.json"
+ },
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": []
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/generateddocs/roleAssignmentSubscription.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentSubscription.bicep.md
new file mode 100644
index 0000000..59081a8
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentSubscription.bicep.md
@@ -0,0 +1,80 @@
+# ALZ Bicep - Role Assignment to a Subscription
+
+Module used to create a Role Assignment to a Subscription
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parRoleAssignmentNameGuid | No | A GUID representing the role assignment name.
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry.
+
+### parRoleAssignmentNameGuid
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A GUID representing the role assignment name.
+
+- Default value: `[guid(subscription().subscriptionId, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]`
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry.
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentSubscription.json"
+ },
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "[guid(subscription().subscriptionId, parameters('parRoleDefinitionId'), parameters('parAssigneeObjectId'))]"
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/generateddocs/roleAssignmentSubscriptionMany.bicep.md b/common/roleAssignments/generateddocs/roleAssignmentSubscriptionMany.bicep.md
new file mode 100644
index 0000000..67a3464
--- /dev/null
+++ b/common/roleAssignments/generateddocs/roleAssignmentSubscriptionMany.bicep.md
@@ -0,0 +1,78 @@
+# ALZ Bicep - Role Assignment to Subscriptions
+
+Module used to create a Role Assignment to multiple Subscriptions
+
+## Parameters
+
+Parameter name | Required | Description
+-------------- | -------- | -----------
+parSubscriptionIds | No | A list of subscription IDs that will be used for role assignment (i.e. 4f9f8765-911a-4a6d-af60-4bc0473268c0).
+parRoleDefinitionId | Yes | Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+parAssigneePrincipalType | Yes | Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+parAssigneeObjectId | Yes | Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry
+
+### parSubscriptionIds
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+A list of subscription IDs that will be used for role assignment (i.e. 4f9f8765-911a-4a6d-af60-4bc0473268c0).
+
+### parRoleDefinitionId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)
+
+### parAssigneePrincipalType
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Principal type of the assignee. Allowed values are 'Group' (Security Group) or 'ServicePrincipal' (Service Principal or System/User Assigned Managed Identity)
+
+- Allowed values: `Group`, `ServicePrincipal`
+
+### parAssigneeObjectId
+
+![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square)
+
+Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID
+
+### parTelemetryOptOut
+
+![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square)
+
+Set Parameter to true to Opt-out of deployment telemetry
+
+- Default value: `False`
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/roleAssignmentSubscriptionMany.json"
+ },
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": []
+ },
+ "parRoleDefinitionId": {
+ "value": ""
+ },
+ "parAssigneePrincipalType": {
+ "value": ""
+ },
+ "parAssigneeObjectId": {
+ "value": ""
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
+```
diff --git a/common/roleAssignments/media/bicepVisualizerMg.png b/common/roleAssignments/media/bicepVisualizerMg.png
new file mode 100644
index 0000000..67ff829
Binary files /dev/null and b/common/roleAssignments/media/bicepVisualizerMg.png differ
diff --git a/common/roleAssignments/media/bicepVisualizerMgMany.png b/common/roleAssignments/media/bicepVisualizerMgMany.png
new file mode 100644
index 0000000..7dc7d43
Binary files /dev/null and b/common/roleAssignments/media/bicepVisualizerMgMany.png differ
diff --git a/common/roleAssignments/media/bicepVisualizerSub.png b/common/roleAssignments/media/bicepVisualizerSub.png
new file mode 100644
index 0000000..67ff829
Binary files /dev/null and b/common/roleAssignments/media/bicepVisualizerSub.png differ
diff --git a/common/roleAssignments/media/bicepVisualizerSubMany.png b/common/roleAssignments/media/bicepVisualizerSubMany.png
new file mode 100644
index 0000000..971a304
Binary files /dev/null and b/common/roleAssignments/media/bicepVisualizerSubMany.png differ
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..12c90c3
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..4501e72
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.managedIdentity.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.all.json
new file mode 100644
index 0000000..8851ff7
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.min.json
new file mode 100644
index 0000000..bc5415e
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.securityGroup.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..12c90c3
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..4501e72
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroup.servicePrincipal.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..1e52c0b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..1e52c0b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.managedIdentity.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.all.json
new file mode 100644
index 0000000..11fd45b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.min.json
new file mode 100644
index 0000000..11fd45b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.securityGroup.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..1e52c0b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..1e52c0b
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentManagementGroupMany.servicePrincipal.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parManagementGroupIds": {
+ "value": [
+ "alz-platform-connectivity",
+ "alz-platform-identity"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..391a338
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..1fabe92
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.managedIdentity.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.all.json
new file mode 100644
index 0000000..c5d868f
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.min.json
new file mode 100644
index 0000000..084bb34
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.securityGroup.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..391a338
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..1fabe92
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroup.servicePrincipal.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..b710c39
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..b710c39
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.managedIdentity.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.all.json
new file mode 100644
index 0000000..84825a5
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.min.json
new file mode 100644
index 0000000..84825a5
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.securityGroup.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..b710c39
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..b710c39
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentResourceGroupMany.servicePrincipal.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parResourceGroupIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..12c90c3
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..4501e72
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.managedIdentity.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.all.json
new file mode 100644
index 0000000..8851ff7
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.min.json
new file mode 100644
index 0000000..bc5415e
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.securityGroup.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..12c90c3
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.all.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleAssignmentNameGuid": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..4501e72
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscription.servicePrincipal.parameters.min.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.all.json
new file mode 100644
index 0000000..bae2220
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.min.json
new file mode 100644
index 0000000..bae2220
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.managedIdentity.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.all.json
new file mode 100644
index 0000000..034a798
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.min.json
new file mode 100644
index 0000000..034a798
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.securityGroup.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "Group"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.all.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.all.json
new file mode 100644
index 0000000..bae2220
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.all.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.min.json b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.min.json
new file mode 100644
index 0000000..bae2220
--- /dev/null
+++ b/common/roleAssignments/parameters/roleAssignmentSubscriptionMany.servicePrincipal.parameters.min.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parSubscriptionIds": {
+ "value": [
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ },
+ "parRoleDefinitionId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parAssigneePrincipalType": {
+ "value": "ServicePrincipal"
+ },
+ "parAssigneeObjectId": {
+ "value": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ },
+ "parTelemetryOptOut": {
+ "value": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/roleAssignments/roleAssignmentManagementGroup.bicep b/common/roleAssignments/roleAssignmentManagementGroup.bicep
new file mode 100644
index 0000000..bf15de4
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentManagementGroup.bicep
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to a management group.
+DESCRIPTION: This module will create a deployment at management group level which will assign a role to a management group and optional deployment for Customer Usage Attribution.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+metadata name = 'ALZ Bicep - Role Assignment to a Management Group'
+metadata description = 'Module used to assign a role to Management Group'
+
+@sys.description('A GUID representing the role assignment name.')
+param parRoleAssignmentNameGuid string = guid(managementGroup().name, parRoleDefinitionId, parAssigneeObjectId)
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry.')
+param parTelemetryOptOut bool = false
+
+// Customer Usage Attribution Id
+var varCuaid = '59c2ac61-cd36-413b-b999-86a3e0d958fb'
+
+resource resRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: parRoleAssignmentNameGuid
+ properties: {
+ roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions', parRoleDefinitionId)
+ principalId: parAssigneeObjectId
+ principalType: parAssigneePrincipalType
+ }
+}
+
+// Optional Deployment for Customer Usage Attribution
+module modCustomerUsageAttribution '../CRML/customerUsageAttribution/cuaIdManagementGroup.bicep' = if (!parTelemetryOptOut) {
+ #disable-next-line no-loc-expr-outside-params //Only to ensure telemetry data is stored in same location as deployment. See https://github.com/Azure/ALZ-Bicep/wiki/FAQ#why-are-some-linter-rules-disabled-via-the-disable-next-line-bicep-function for more information
+ name: 'pid-${varCuaid}-${uniqueString(deployment().location, parRoleAssignmentNameGuid)}'
+ params: {}
+}
diff --git a/common/roleAssignments/roleAssignmentManagementGroupMany.bicep b/common/roleAssignments/roleAssignmentManagementGroupMany.bicep
new file mode 100644
index 0000000..e735371
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentManagementGroupMany.bicep
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to multiple management groups.
+DESCRIPTION: This module will create a deployment at management group level which will assign a role to multiple management groups.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+metadata name = 'ALZ Bicep - Role Assignment to Management Groups'
+metadata description = 'Module used to assign a Role Assignment to multiple Management Groups'
+
+@sys.description('A list of management group scopes that will be used for role assignment (i.e. [alz-platform-connectivity, alz-platform-identity]).')
+param parManagementGroupIds array = []
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry')
+param parTelemetryOptOut bool = false
+
+module modRoleAssignment 'roleAssignmentManagementGroup.bicep' = [for parManagementGroupId in parManagementGroupIds: {
+ name: 'rbac-assign-${uniqueString(parManagementGroupId, parAssigneeObjectId, parRoleDefinitionId)}'
+ scope: managementGroup(parManagementGroupId)
+ params: {
+ parRoleAssignmentNameGuid: guid(parManagementGroupId, parRoleDefinitionId, parAssigneeObjectId)
+ parAssigneeObjectId: parAssigneeObjectId
+ parAssigneePrincipalType: parAssigneePrincipalType
+ parRoleDefinitionId: parRoleDefinitionId
+ parTelemetryOptOut: parTelemetryOptOut
+ }
+}]
diff --git a/common/roleAssignments/roleAssignmentResourceGroup.bicep b/common/roleAssignments/roleAssignmentResourceGroup.bicep
new file mode 100644
index 0000000..e2295cd
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentResourceGroup.bicep
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to a resource group.
+DESCRIPTION: This module will create a deployment which will assign a role to a resource group and optional deployment for Customer Usage Attribution.
+AUTHOR/S: Cloud for Sovereignty
+*/
+metadata name = 'ALZ Bicep - Role Assignment to a Resource Group'
+metadata description = 'Module used to assign a Role Assignment to a Resource Group'
+
+@sys.description('A GUID representing the role assignment name.')
+param parRoleAssignmentNameGuid string = guid(resourceGroup().id, parRoleDefinitionId, parAssigneeObjectId)
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry.')
+param parTelemetryOptOut bool = false
+
+// Customer Usage Attribution Id
+var varCuaid = '59c2ac61-cd36-413b-b999-86a3e0d958fb'
+
+resource resRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: parRoleAssignmentNameGuid
+ properties: {
+ roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', parRoleDefinitionId)
+ principalId: parAssigneeObjectId
+ principalType: parAssigneePrincipalType
+ }
+}
+
+// Optional Deployment for Customer Usage Attribution
+module modCustomerUsageAttribution '../CRML/customerUsageAttribution/cuaIdSubscription.bicep' = if (!parTelemetryOptOut) {
+ name: 'pid-${varCuaid}-${uniqueString(resourceGroup().id, parAssigneeObjectId)}'
+ params: {}
+ scope: subscription()
+}
diff --git a/common/roleAssignments/roleAssignmentResourceGroupMany.bicep b/common/roleAssignments/roleAssignmentResourceGroupMany.bicep
new file mode 100644
index 0000000..9e8a638
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentResourceGroupMany.bicep
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to multiple resource groups.
+DESCRIPTION: This module will create a deployment which will assign a role to multiple resource groups.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+metadata name = 'ALZ Bicep - Role Assignment to Resource Groups'
+metadata description = 'Module used to assign a Role Assignment to multiple Resource Groups'
+
+@sys.description('A list of Resource Groups that will be used for role assignment in the format of subscriptionId/resourceGroupName (i.e. a1fe8a74-e0ac-478b-97ea-24a27958961b/rg01).')
+param parResourceGroupIds array = []
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry')
+param parTelemetryOptOut bool = false
+
+module modRoleAssignment 'roleAssignmentResourceGroup.bicep' = [for resourceGroupId in parResourceGroupIds: {
+ name: 'rbac-assign-${uniqueString(resourceGroupId, parAssigneeObjectId, parRoleDefinitionId)}'
+ scope: resourceGroup(split(resourceGroupId, '/')[0], split(resourceGroupId, '/')[1])
+ params: {
+ parRoleAssignmentNameGuid: guid(resourceGroupId, parRoleDefinitionId, parAssigneeObjectId)
+ parAssigneeObjectId: parAssigneeObjectId
+ parAssigneePrincipalType: parAssigneePrincipalType
+ parRoleDefinitionId: parRoleDefinitionId
+ parTelemetryOptOut: parTelemetryOptOut
+ }
+}]
diff --git a/common/roleAssignments/roleAssignmentSubscription.bicep b/common/roleAssignments/roleAssignmentSubscription.bicep
new file mode 100644
index 0000000..755e20f
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentSubscription.bicep
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to a subscription.
+DESCRIPTION: This module will create a deployment which will assign a role to a subscription and optional deployment for Customer Usage Attribution.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'subscription'
+
+metadata name = 'ALZ Bicep - Role Assignment to a Subscription'
+metadata description = 'Module used to assign a Role Assignment to a Subscription'
+
+@sys.description('A GUID representing the role assignment name.')
+param parRoleAssignmentNameGuid string = guid(subscription().subscriptionId, parRoleDefinitionId, parAssigneeObjectId)
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry.')
+param parTelemetryOptOut bool = false
+
+// Customer Usage Attribution Id
+var varCuaid = '59c2ac61-cd36-413b-b999-86a3e0d958fb'
+
+resource resRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: parRoleAssignmentNameGuid
+ properties: {
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parRoleDefinitionId)
+ principalId: parAssigneeObjectId
+ principalType: parAssigneePrincipalType
+ }
+}
+
+// Optional Deployment for Customer Usage Attribution
+module modCustomerUsageAttribution '../CRML/customerUsageAttribution/cuaIdSubscription.bicep' = if (!parTelemetryOptOut) {
+ name: 'pid-${varCuaid}-${uniqueString(subscription().subscriptionId, parAssigneeObjectId)}'
+ params: {}
+}
diff --git a/common/roleAssignments/roleAssignmentSubscriptionMany.bicep b/common/roleAssignments/roleAssignmentSubscriptionMany.bicep
new file mode 100644
index 0000000..9ee6185
--- /dev/null
+++ b/common/roleAssignments/roleAssignmentSubscriptionMany.bicep
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to assign a role to multiple subscriptions.
+DESCRIPTION: This module will create a deployment which will assign a role to multiple subscriptions.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+metadata name = 'ALZ Bicep - Role Assignment to Subscriptions'
+metadata description = 'Module used to assign a Role Assignment to multiple Subscriptions'
+
+@sys.description('A list of subscription IDs that will be used for role assignment (i.e. 4f9f8765-911a-4a6d-af60-4bc0473268c0).')
+param parSubscriptionIds array = []
+
+@sys.description('Role Definition Id (i.e. GUID, Reader Role Definition ID: acdd72a7-3385-48ef-bd42-f606fba81ae7)')
+param parRoleDefinitionId string
+
+@sys.description('Principal type of the assignee. Allowed values are \'Group\' (Security Group) or \'ServicePrincipal\' (Service Principal or System/User Assigned Managed Identity)')
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+param parAssigneePrincipalType string
+
+@sys.description('Object ID of groups, service principals or managed identities. For managed identities use the principal id. For service principals, use the object ID and not the app ID')
+param parAssigneeObjectId string
+
+@sys.description('Set Parameter to true to Opt-out of deployment telemetry')
+param parTelemetryOptOut bool = false
+
+module modRoleAssignment 'roleAssignmentSubscription.bicep' = [for subscriptionId in parSubscriptionIds: {
+ name: 'rbac-assign-${uniqueString(subscriptionId, parAssigneeObjectId, parRoleDefinitionId)}'
+ scope: subscription(subscriptionId)
+ params: {
+ parRoleAssignmentNameGuid: guid(subscriptionId, parRoleDefinitionId, parAssigneeObjectId)
+ parAssigneeObjectId: parAssigneeObjectId
+ parAssigneePrincipalType: parAssigneePrincipalType
+ parRoleDefinitionId: parRoleDefinitionId
+ parTelemetryOptOut: parTelemetryOptOut
+ }
+}]
diff --git a/common/roleAssignments/samples/baseline.sample.bicep b/common/roleAssignments/samples/baseline.sample.bicep
new file mode 100644
index 0000000..b40f0f5
--- /dev/null
+++ b/common/roleAssignments/samples/baseline.sample.bicep
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Baseline deployment sample
+DESCRIPTION: Sample Assigns owner-role (8e3af657-a8ff-443c-a75c-2fe8c4bcb635) to a principal with the ID 00000000-0000-0000-0000-000000000000.
+The principal ID needs to be changed ot a principal that exists in the target tenant.
+Use this sample to deploy the minimum resource configuration.
+AUTHOR/S: Cloud for Sovereignty
+*/
+
+targetScope = 'managementGroup'
+
+// ----------
+// PARAMETERS
+// ----------
+var roleDefinitionId = '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
+var assigneeObjectId = '00000000-0000-0000-0000-000000000000'
+// ---------
+// RESOURCES
+// ---------
+
+@description('Baseline resource configuration.')
+module baseline_ra '../roleAssignmentManagementGroup.bicep' = {
+ name: 'baseline_ra'
+ params: {
+ parRoleDefinitionId: roleDefinitionId
+ parAssigneePrincipalType: 'Group'
+ parAssigneeObjectId: assigneeObjectId
+ parTelemetryOptOut: true
+ parRoleAssignmentNameGuid: guid(managementGroup().name, roleDefinitionId, assigneeObjectId)
+ }
+}
diff --git a/common/roleAssignments/samples/generateddocs/baseline.sample.bicep.md b/common/roleAssignments/samples/generateddocs/baseline.sample.bicep.md
new file mode 100644
index 0000000..2218747
--- /dev/null
+++ b/common/roleAssignments/samples/generateddocs/baseline.sample.bicep.md
@@ -0,0 +1,16 @@
+# Azure template
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/samples/baseline.sample.json"
+ },
+ "parameters": {}
+}
+```
diff --git a/common/roleAssignments/samples/generateddocs/minimum.sample.bicep.md b/common/roleAssignments/samples/generateddocs/minimum.sample.bicep.md
new file mode 100644
index 0000000..b690af8
--- /dev/null
+++ b/common/roleAssignments/samples/generateddocs/minimum.sample.bicep.md
@@ -0,0 +1,16 @@
+# Azure template
+
+## Snippets
+
+### Parameter file
+
+```json
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "template": "infra-as-code/bicep/modules/roleAssignments/samples/minimum.sample.json"
+ },
+ "parameters": {}
+}
+```
diff --git a/common/roleAssignments/samples/minimum.sample.bicep b/common/roleAssignments/samples/minimum.sample.bicep
new file mode 100644
index 0000000..9f3b17d
--- /dev/null
+++ b/common/roleAssignments/samples/minimum.sample.bicep
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Minimum deployment sample
+DESCRIPTION: Sample Assigns reader-role (acdd72a7-3385-48ef-bd42-f606fba81ae7) to a principal with the ID 00000000-0000-0000-0000-000000000000.
+The principal ID needs to be changed ot a principal that exists in the target tenant.
+Use this sample to deploy the minimum resource configuration.
+AUTHOR/S: Cloud for Sovereignty
+*/
+
+targetScope = 'managementGroup'
+
+// ----------
+// PARAMETERS
+// ----------
+
+
+// ---------
+// RESOURCES
+// ---------
+
+@description('Minimum resource configuration.')
+module ra_mg'../roleAssignmentManagementGroup.bicep' = {
+ name: 'ra_mg'
+ params: {
+ parRoleDefinitionId: 'acdd72a7-3385-48ef-bd42-f606fba81ae7'
+ parAssigneePrincipalType: 'Group'
+ parAssigneeObjectId: '00000000-0000-0000-0000-000000000000'
+ }
+}
diff --git a/sovereignApplications/README.md b/sovereignApplications/README.md
new file mode 100644
index 0000000..86ba78e
--- /dev/null
+++ b/sovereignApplications/README.md
@@ -0,0 +1,5 @@
+# Confidential sample application for Sovereign Landing Zone
+
+The confidential sample application is described [here](../sovereignApplications/confidential/hrAppWorkload/README.md). There are two parts of the application:
+* Confidential services which represent the main part of the app
+* Non-confidential services which are auxilary parts, like [admin VM](./nonConfidential/adminVM/README.md) and [storage account](./nonConfidential/storageAccount/README.md)
diff --git a/sovereignApplications/confidential/azureConfidentialLedger/README.md b/sovereignApplications/confidential/azureConfidentialLedger/README.md
new file mode 100644
index 0000000..e868ec4
--- /dev/null
+++ b/sovereignApplications/confidential/azureConfidentialLedger/README.md
@@ -0,0 +1,3 @@
+# Contoso HR Sample Application - Confidential Ledger Bicep Module
+
+This sample application is part of the [hrAppWorkload](../hrAppWorkload) confidential sample application and is a Bicep module that deploys the Azure Confidential Ledger resources required by the Contoso HR sample application.
\ No newline at end of file
diff --git a/sovereignApplications/confidential/azureConfidentialLedger/template.acl.bicep b/sovereignApplications/confidential/azureConfidentialLedger/template.acl.bicep
new file mode 100644
index 0000000..1faf423
--- /dev/null
+++ b/sovereignApplications/confidential/azureConfidentialLedger/template.acl.bicep
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to create the azure confidential ledger.
+DESCRIPTION: This module will create a deployment which will create the azure confidential ledger.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Azure Confidential Ledger Name')
+@minLength(3)
+@maxLength(24)
+param parLedgerName string
+
+@description('Resource tags')
+param parTags object
+
+@description('Set of Object IDs for Azure AD users who are set as admins on the Confidential Ledger.')
+param parAdministratorUserObjectIds array
+
+@description('Deployment location for all resources.')
+param parDeploymentLocation string
+
+resource resLedger 'Microsoft.ConfidentialLedger/ledgers@2022-05-13' = {
+ name: parLedgerName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ ledgerType: 'Public'
+ aadBasedSecurityPrincipals: [for objectId in parAdministratorUserObjectIds: {
+ principalId: objectId
+ ledgerRoleName: 'Administrator'
+ }]
+ }
+}
diff --git a/.gitignore b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/.gitignore
similarity index 99%
rename from .gitignore
rename to sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/.gitignore
index 8a30d25..38e05ef 100644
--- a/.gitignore
+++ b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/.gitignore
@@ -10,6 +10,9 @@
*.userosscache
*.sln.docstates
+# Local JS Packages
+wwwroot/lib/*
+
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.cs b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.cs
new file mode 100644
index 0000000..9356c83
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Azure.Identity;
+using Azure.Security.ConfidentialLedger;
+using System.Text.Json;
+
+///
+/// This tool connects to an Azure Confidential Ledger.
+/// And retrieves logs under the "ContosoHrSqlLogs" collection.
+///
+
+if (args.Length != 1)
+{
+ Console.WriteLine("Usage: dotnet run ");
+ Environment.Exit(1);
+}
+
+string ledgerName = args[0];
+var ledgerUri = $"https://{ledgerName}.confidential-ledger.azure.com";
+var ledgerClient = new ConfidentialLedgerClient(new Uri(ledgerUri), new DefaultAzureCredential());
+
+await foreach (var data in ledgerClient.GetLedgerEntriesAsync("ContosoHrSqlLogs"))
+{
+ JsonElement result = JsonDocument.Parse(data.ToStream()).RootElement;
+ Console.WriteLine(result.GetProperty("contents").ToString());
+}
diff --git a/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.csproj b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.csproj
new file mode 100644
index 0000000..2d7fcb0
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/ConfidentialLedgerClient.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/README.md b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/README.md
new file mode 100644
index 0000000..ea5ce92
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/ConfidentialLedgerClient/README.md
@@ -0,0 +1,42 @@
+# Confidential Ledger Client
+
+This tool connects to an Azure Confidential Ledger and retrieves and prints the contents of all entries under the "ContosoHrSqlLogs" collection.
+
+## Prerequisites
+
+To build and run this CLI client you need the .NET 6 SDK installed, which you can download from the [.NET website](https://dotnet.microsoft.com/download/dotnet). Alternatively, if you are on a machine that has the Windows Package Manager (winget), you can install it through a one-line command:
+
+```
+winget install Microsoft.DotNet.SDK.6
+```
+
+> [!NOTE]
+> After installing .NET you may need to close and reopen any terminal windows, to ensure your PATH is updated to include the `dotnet` command.
+
+### ADO Users
+
+If you downloaded this code from a source that enforces the use of a particular nuget package stream and if you must authenticate to access that package stream (e.g., Azure DevOps), you may need to take additional steps to successfully build the client.
+
+1. Install the *Azure Artifacts Credential Provider* as follows:
+ ``` powershell
+ iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) }"
+ ```
+2.
+3. Restore your project with the `--interactive` flag and go through the browser-based authentication flow to generate a token granting access to the package stream:
+ ```
+ dotnet restore --interactive
+ ```
+4. Note that this login is used to access the package stream; it may be different from the login subsequently used to read the ledger.
+
+## Usage
+
+Connect to the Azure subscription that contains the ledger you want to read, then run the client with the name of the ledger as the only argument:
+
+```
+Connect-AzAccount -Subscription
+dotnet run
+```
+
+## Known issue
+
+The `ConfidentialLedger.ConfidentialLedgerClient` will occasionally fail to connect to the ledger due to an SSL connection issue that is as yet unresolved. If this happens, retries often work.
\ No newline at end of file
diff --git a/sovereignApplications/confidential/contosoHR/README.md b/sovereignApplications/confidential/contosoHR/README.md
new file mode 100644
index 0000000..e2bae80
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/README.md
@@ -0,0 +1,13 @@
+# Contoso HR Sample Application Source Code
+
+This sample application is part of the [hrAppWorkload](../hrAppWorkload) confidential sample application. It is a simple web application that allows users to view and update employee information. The application is built using the following technologies:
+
+* .NET 6.0 (cross-platform)
+* Entity Framework Core
+* Client libraries for:
+ - Azure SQL Database
+ - Azure KeyVault
+ - Azure Confidential Ledger
+ - Azure Active Directory
+
+For more details, please navigate through the documentation in the [hrAppWorkload](../hrAppWorkload) confidential sample application folder.
diff --git a/sovereignApplications/confidential/contosoHR/config/cloud-init.yaml b/sovereignApplications/confidential/contosoHR/config/cloud-init.yaml
new file mode 100644
index 0000000..32363a4
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/config/cloud-init.yaml
@@ -0,0 +1,35 @@
+#cloud-config
+package_upgrade: true
+packages:
+- dotnet-sdk-6.0
+- libarchive-tools
+users:
+ - default
+ - name: contosohr
+ shell: /bin/bash
+write_files:
+- path: /etc/systemd/system/ContosoHR.service
+ owner: root:root
+ content: |
+ [Unit]
+ Description=ContosoHR
+
+ [Service]
+ Type=simple
+ ExecStart=/usr/bin/dotnet run --launch-profile Production
+ WorkingDirectory=/home/contosohr/ContosoHR
+ Restart=always
+ RestartSec=10
+ User=contosohr
+ Group=contosohr
+
+ [Install]
+ WantedBy=multi-user.target
+runcmd:
+- wget https://hrwebapp.blob.core.windows.net/contosohr/ContosoHR.zip
+- mkdir --parents /home/contosohr/ContosoHR
+- bsdtar --extract --file ContosoHR.zip --directory /home/contosohr/ContosoHR --strip-components=1
+- chown --recursive contosohr:contosohr /home/contosohr/
+- systemctl daemon-reload
+- systemctl enable ContosoHR.service
+- systemctl start ContosoHR.service
\ No newline at end of file
diff --git a/sovereignApplications/confidential/contosoHR/data/data.sql b/sovereignApplications/confidential/contosoHR/data/data.sql
new file mode 100644
index 0000000..bf53562
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/data/data.sql
@@ -0,0 +1,24 @@
+-- Copyright (c) Microsoft Corporation.
+-- Licensed under the MIT License.
+--- SUMMARY: Insert employee data ---
+INSERT INTO [HR].[Employees]
+ ([SSN]
+ ,[FirstName]
+ ,[LastName]
+ ,[Salary])
+ VALUES
+ ('795-73-9838'
+ , N'Catherine'
+ , N'Abel'
+ , $31692);
+
+INSERT INTO [HR].[Employees]
+ ([SSN]
+ ,[FirstName]
+ ,[LastName]
+ ,[Salary])
+ VALUES
+ ('990-00-6818'
+ , N'Kim'
+ , N'Abercrombie'
+ , $55415);
diff --git a/sovereignApplications/confidential/contosoHR/data/encryption.sql b/sovereignApplications/confidential/contosoHR/data/encryption.sql
new file mode 100644
index 0000000..541e2b6
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/data/encryption.sql
@@ -0,0 +1,16 @@
+-- Copyright (c) Microsoft Corporation.
+-- Licensed under the MIT License.
+--- SUMMARY: Encrypt employee SSN and Salary columns ---
+ALTER TABLE [HR].[Employees]
+ALTER COLUMN [SSN] [char] (11) COLLATE Latin1_General_BIN2
+ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
+WITH
+(ONLINE = ON);
+
+ALTER TABLE [HR].[Employees]
+ALTER COLUMN [Salary] [decimal](19,4)
+ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
+WITH
+(ONLINE = ON);
+
+ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
diff --git a/sovereignApplications/confidential/contosoHR/data/schema.sql b/sovereignApplications/confidential/contosoHR/data/schema.sql
new file mode 100644
index 0000000..5d767cb
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/data/schema.sql
@@ -0,0 +1,11 @@
+-- Copyright (c) Microsoft Corporation.
+-- Licensed under the MIT License.
+--- SUMMARY: Create employee table ---
+CREATE TABLE [HR].[Employees]
+(
+ [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
+ [SSN] [char](11) NOT NULL,
+ [FirstName] [nvarchar](50) NOT NULL,
+ [LastName] [nvarchar](50) NOT NULL,
+ [Salary] [decimal](19,4) NOT NULL
+) ON [PRIMARY];
diff --git a/sovereignApplications/confidential/contosoHR/src/.config/dotnet-tools.json b/sovereignApplications/confidential/contosoHR/src/.config/dotnet-tools.json
new file mode 100644
index 0000000..b0e38ab
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/.config/dotnet-tools.json
@@ -0,0 +1,5 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {}
+}
\ No newline at end of file
diff --git a/sovereignApplications/confidential/contosoHR/src/.gitignore b/sovereignApplications/confidential/contosoHR/src/.gitignore
new file mode 100644
index 0000000..38e05ef
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/.gitignore
@@ -0,0 +1,401 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Local JS Packages
+wwwroot/lib/*
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
diff --git a/sovereignApplications/confidential/contosoHR/src/ContosoHR.csproj b/sovereignApplications/confidential/contosoHR/src/ContosoHR.csproj
new file mode 100644
index 0000000..73ce33b
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/ContosoHR.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net6.0
+ 54e8ce16-db31-4c81-a83f-e7f742cb59d9
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/ContosoHR.sln b/sovereignApplications/confidential/contosoHR/src/ContosoHR.sln
new file mode 100644
index 0000000..5bf4505
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/ContosoHR.sln
@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33502.453
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContosoHR", "ContosoHR.csproj", "{91C0C152-3656-4460-8C0C-DAE0505E26FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfidentialLedgerClient", "..\ConfidentialLedgerClient\ConfidentialLedgerClient.csproj", "{B011B98C-CB70-4271-A368-AF9BA21768A5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {91C0C152-3656-4460-8C0C-DAE0505E26FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {91C0C152-3656-4460-8C0C-DAE0505E26FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {91C0C152-3656-4460-8C0C-DAE0505E26FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {91C0C152-3656-4460-8C0C-DAE0505E26FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B011B98C-CB70-4271-A368-AF9BA21768A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B011B98C-CB70-4271-A368-AF9BA21768A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B011B98C-CB70-4271-A368-AF9BA21768A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B011B98C-CB70-4271-A368-AF9BA21768A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {09B03C29-04E6-4C93-9C58-C16B6A907DAE}
+ EndGlobalSection
+EndGlobal
diff --git a/sovereignApplications/confidential/contosoHR/src/Controllers/EmployeeController.cs b/sovereignApplications/confidential/contosoHR/src/Controllers/EmployeeController.cs
new file mode 100644
index 0000000..f457d09
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Controllers/EmployeeController.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Data;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+
+///
+/// This is ContosoHR API controller to retrieve the employee entries from database.
+///
+namespace ContosoHR.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class EmployeeController : ControllerBase
+ {
+ private readonly ContosoHRContext context;
+ private readonly IConfiguration configuration;
+ public EmployeeController(ContosoHRContext context, IConfiguration configuration)
+ {
+ this.context = context;
+ this.configuration = configuration;
+ }
+ [HttpPost]
+ public IActionResult GetEmployees()
+ {
+ var draw = Request.Form["draw"].FirstOrDefault();
+ var start = Request.Form["start"].FirstOrDefault();
+ var length = Request.Form["length"].FirstOrDefault();
+ var sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault();
+ var sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault();
+ var searchValue = Request.Form["search[value]"].FirstOrDefault();
+ int pageSize = length != null ? Convert.ToInt32(length) : 0;
+ int skip = start != null ? Convert.ToInt32(start) : 0;
+ int recordsTotal = 0;
+
+ string salaryRange = Request.Form["columns[4][search][value]"]; // NOTE: it must match .column(8) in Index.cshtml
+
+
+ int from = 0, to = 100000;
+
+ if (!string.IsNullOrEmpty(salaryRange))
+ {
+ from = Convert.ToInt32(salaryRange.Split(':')[0]);
+ to = Convert.ToInt32(salaryRange.Split(':')[1]);
+ }
+
+ var ssnSearchPattern = new SqlParameter();
+ ssnSearchPattern.ParameterName = @"@SSNSearchPattern";
+ ssnSearchPattern.DbType = DbType.AnsiStringFixedLength;
+ ssnSearchPattern.Direction = ParameterDirection.Input;
+ ssnSearchPattern.Value = "%" + searchValue + "%";
+ ssnSearchPattern.Size = ssnSearchPattern.Value.ToString().Length;
+
+ var nameSearchPattern = new SqlParameter();
+ nameSearchPattern.ParameterName = @"@NameSearchPattern";
+ nameSearchPattern.DbType = DbType.String;
+ nameSearchPattern.Direction = ParameterDirection.Input;
+ nameSearchPattern.Value = "%" + searchValue + "%";
+ nameSearchPattern.Size = nameSearchPattern.Value.ToString().Length;
+
+ var minSalary = new SqlParameter();
+ minSalary.ParameterName = @"@MinSalary";
+ minSalary.DbType = DbType.Decimal;
+ minSalary.Precision = 19;
+ minSalary.Scale = 4;
+ minSalary.Direction = ParameterDirection.Input;
+ minSalary.Value = from;
+
+ var maxSalary = new SqlParameter();
+ maxSalary.ParameterName = @"@MaxSalary";
+ maxSalary.DbType = DbType.Decimal;
+ maxSalary.Precision = 19;
+ maxSalary.Scale = 4;
+ maxSalary.Direction = ParameterDirection.Input;
+ maxSalary.Value = to;
+
+ var employeeData = context.Employees.FromSqlRaw(
+ @"SELECT [EmployeeID], [SSN], [FirstName], [LastName], [Salary] FROM [HR].[Employees] WHERE ([SSN] LIKE @SSNSearchPattern OR [LastName] LIKE @NameSearchPattern) AND [Salary] BETWEEN @MinSalary AND @MaxSalary"
+ , ssnSearchPattern
+ , nameSearchPattern
+ , minSalary
+ , maxSalary);
+
+ if (!(string.IsNullOrEmpty(sortColumn) && string.IsNullOrEmpty(sortColumnDirection)))
+ {
+ employeeData = employeeData.OrderBy(sortColumn + " " + sortColumnDirection);
+ }
+
+ recordsTotal = employeeData.Count();
+ var data = employeeData.Skip(skip).Take(pageSize).ToList();
+ var jsonData = new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data };
+ return Ok(jsonData);
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Models/ContosoHRContext.cs b/sovereignApplications/confidential/contosoHR/src/Models/ContosoHRContext.cs
new file mode 100644
index 0000000..dbfca3c
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Models/ContosoHRContext.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Util;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+///
+/// This is Contoso HR Context.
+///
+
+#nullable disable
+namespace ContosoHR.Models
+{
+ public partial class ContosoHRContext : DbContext
+ {
+ ConfidentialLedgerLogger _logger;
+
+ public ContosoHRContext(DbContextOptions options, ConfidentialLedgerLogger logger)
+ : base(options)
+ {
+ _logger = logger;
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.LogTo(_logger.Log, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);
+
+ public virtual DbSet Employees { get; set; }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Models/Employee.cs b/sovereignApplications/confidential/contosoHR/src/Models/Employee.cs
new file mode 100644
index 0000000..fabde53
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Models/Employee.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+///
+/// This is the employees data schema.
+///
+
+#nullable disable
+
+namespace ContosoHR.Models
+{
+ [Table("Employees", Schema = "HR")]
+ public partial class Employee
+ {
+ public int EmployeeId { get; set; }
+ [RegularExpression(@"^\d{3}-\d{2}-\d{4}$", ErrorMessage = "Invalid SSN format.")]
+ [Column(TypeName = "char(11)")]
+ public string Ssn { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ [Column(TypeName = "decimal(19,4)")]
+ public decimal Salary { get; set; }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml
new file mode 100644
index 0000000..61b20c4
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml
@@ -0,0 +1,52 @@
+@page
+@model ContosoHR.Pages.Employees.CreateModel
+
+@{
+ ViewData["Title"] = "Create";
+}
+
+Create
+
+Employee
+
+
+
+
+
+@section Scripts {
+ @{
+ await Html.RenderPartialAsync("_ValidationScriptsPartial");
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml.cs
new file mode 100644
index 0000000..5f8d932
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Create.cshtml.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+
+///
+/// Create a new employee entry
+///
+namespace ContosoHR.Pages.Employees
+{
+ public class CreateModel : PageModel
+ {
+ private readonly ContosoHR.Models.ContosoHRContext _context;
+
+ public CreateModel(ContosoHR.Models.ContosoHRContext context)
+ {
+ _context = context;
+ }
+
+ public IActionResult OnGet()
+ {
+ return Page();
+ }
+
+ [BindProperty]
+ public Employee Employee { get; set; }
+
+ // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
+ public async Task OnPostAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ try
+ {
+ var validationContext = new ValidationContext(Employee);
+ Validator.ValidateObject(Employee, validationContext, validateAllProperties: true);
+ _context.Employees.Add(Employee);
+ await _context.SaveChangesAsync();
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ throw;
+ }
+
+ return RedirectToPage("./Index");
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml
new file mode 100644
index 0000000..5045646
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml
@@ -0,0 +1,46 @@
+@page
+@model ContosoHR.Pages.Employees.DeleteModel
+
+@{
+ ViewData["Title"] = "Delete";
+}
+
+Delete
+
+Are you sure you want to delete this?
+
+
Employee
+
+
+
+ @Html.DisplayNameFor(model => model.Employee.Ssn)
+
+
+ @Html.DisplayFor(model => model.Employee.Ssn)
+
+
+ @Html.DisplayNameFor(model => model.Employee.FirstName)
+
+
+ @Html.DisplayFor(model => model.Employee.FirstName)
+
+
+ @Html.DisplayNameFor(model => model.Employee.LastName)
+
+
+ @Html.DisplayFor(model => model.Employee.LastName)
+
+
+ @Html.DisplayNameFor(model => model.Employee.Salary)
+
+
+ @Html.DisplayFor(model => model.Employee.Salary)
+
+
+
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml.cs
new file mode 100644
index 0000000..b69575b
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Delete.cshtml.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.Threading.Tasks;
+
+///
+/// Delete the employee entry
+///
+namespace ContosoHR.Pages.Employees
+{
+ public class DeleteModel : PageModel
+ {
+ private readonly ContosoHR.Models.ContosoHRContext _context;
+
+ public DeleteModel(ContosoHR.Models.ContosoHRContext context)
+ {
+ _context = context;
+ }
+
+ [BindProperty]
+ public Employee Employee { get; set; }
+
+ public async Task OnGetAsync(int? id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
+
+ if (Employee == null)
+ {
+ return NotFound();
+ }
+ return Page();
+ }
+
+ public async Task OnPostAsync(int? id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Employee = await _context.Employees.FindAsync(id);
+
+ if (Employee != null)
+ {
+ _context.Employees.Remove(Employee);
+ await _context.SaveChangesAsync();
+ }
+
+ return RedirectToPage("./Index");
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml
new file mode 100644
index 0000000..f80af9c
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml
@@ -0,0 +1,43 @@
+@page
+@model ContosoHR.Pages.Employees.DetailsModel
+
+@{
+ ViewData["Title"] = "Details";
+}
+
+Details
+
+
+
Employee
+
+
+
+ @Html.DisplayNameFor(model => model.Employee.Ssn)
+
+
+ @Html.DisplayFor(model => model.Employee.Ssn)
+
+
+ @Html.DisplayNameFor(model => model.Employee.FirstName)
+
+
+ @Html.DisplayFor(model => model.Employee.FirstName)
+
+
+ @Html.DisplayNameFor(model => model.Employee.LastName)
+
+
+ @Html.DisplayFor(model => model.Employee.LastName)
+
+
+ @Html.DisplayNameFor(model => model.Employee.Salary)
+
+
+ @Html.DisplayFor(model => model.Employee.Salary)
+
+
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml.cs
new file mode 100644
index 0000000..9191559
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Details.cshtml.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.Threading.Tasks;
+
+///
+/// Get the detail of employee info
+///
+namespace ContosoHR.Pages.Employees
+{
+ public class DetailsModel : PageModel
+ {
+ private readonly ContosoHR.Models.ContosoHRContext _context;
+
+ public DetailsModel(ContosoHR.Models.ContosoHRContext context)
+ {
+ _context = context;
+ }
+
+ public Employee Employee { get; set; }
+
+ public async Task OnGetAsync(int? id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
+
+ if (Employee == null)
+ {
+ return NotFound();
+ }
+ return Page();
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml
new file mode 100644
index 0000000..6986be3
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml
@@ -0,0 +1,50 @@
+@page
+@model ContosoHR.Pages.Employees.EditModel
+
+@{
+ ViewData["Title"] = "Edit";
+}
+
+Edit
+
+Employee
+
+
+
+
+
+@section Scripts {
+ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml.cs
new file mode 100644
index 0000000..d28b04f
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Edit.cshtml.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+
+///
+/// Edit a new employee entry
+///
+namespace ContosoHR.Pages.Employees
+{
+ public class EditModel : PageModel
+ {
+ private readonly ContosoHR.Models.ContosoHRContext _context;
+
+ public EditModel(ContosoHR.Models.ContosoHRContext context)
+ {
+ _context = context;
+ }
+
+ [BindProperty]
+ public Employee Employee { get; set; }
+
+ public async Task OnGetAsync(int? id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == id);
+
+ if (Employee == null)
+ {
+ return NotFound();
+ }
+ return Page();
+ }
+
+ // To protect from overposting attacks, enable the specific properties you want to bind to.
+ // For more details, see https://aka.ms/RazorPagesCRUD.
+ public async Task OnPostAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ try
+ {
+ var validationContext = new ValidationContext(Employee);
+ Validator.ValidateObject(Employee, validationContext, validateAllProperties: true);
+ _context.Attach(Employee).State = EntityState.Modified;
+ await _context.SaveChangesAsync();
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ if (!EmployeeExists(Employee.EmployeeId))
+ {
+ return NotFound();
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ return RedirectToPage("./Index");
+ }
+
+ private bool EmployeeExists(int id)
+ {
+ return _context.Employees.Any(e => e.EmployeeId == id);
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml
new file mode 100644
index 0000000..77ff1a6
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml
@@ -0,0 +1,54 @@
+@page
+@model ContosoHR.Pages.Employees.IndexModel
+
+@{
+ ViewData["Title"] = "Index";
+}
+
+Index
+
+
+ Create New
+
+
+
+
+
+ @Html.DisplayNameFor(model => model.Employee[0].Ssn)
+
+
+ @Html.DisplayNameFor(model => model.Employee[0].FirstName)
+
+
+ @Html.DisplayNameFor(model => model.Employee[0].LastName)
+
+
+ @Html.DisplayNameFor(model => model.Employee[0].Salary)
+
+
+
+
+
+@foreach (var item in Model.Employee) {
+
+
+ @Html.DisplayFor(modelItem => item.Ssn)
+
+
+ @Html.DisplayFor(modelItem => item.FirstName)
+
+
+ @Html.DisplayFor(modelItem => item.LastName)
+
+
+ @Html.DisplayFor(modelItem => item.Salary)
+
+
+ Edit |
+ Details |
+ Delete
+
+
+}
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml.cs
new file mode 100644
index 0000000..11e8a0f
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Employees/Index.cshtml.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using ContosoHR.Models;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+///
+/// Employee Index model
+///
+namespace ContosoHR.Pages.Employees
+{
+ public class IndexModel : PageModel
+ {
+ private readonly ContosoHR.Models.ContosoHRContext _context;
+
+ public IndexModel(ContosoHR.Models.ContosoHRContext context)
+ {
+ _context = context;
+ }
+
+ public IList Employee { get;set; }
+
+ public async Task OnGetAsync()
+ {
+ Employee = await _context.Employees.ToListAsync();
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml
new file mode 100644
index 0000000..6f92b95
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ErrorModel
+@{
+ ViewData["Title"] = "Error";
+}
+
+Error.
+An error occurred while processing your request.
+
+@if (Model.ShowRequestId)
+{
+
+ Request ID: @Model.RequestId
+
+}
+
+Development Mode
+
+ Swapping to the Development environment displays detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml.cs
new file mode 100644
index 0000000..f239e89
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Error.cshtml.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+using System.Diagnostics;
+
+///
+/// Error model
+///
+namespace ContosoHR.Pages
+{
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ [IgnoreAntiforgeryToken]
+ public class ErrorModel : PageModel
+ {
+ public string RequestId { get; set; }
+
+ public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ private readonly ILogger _logger;
+
+ public ErrorModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml
new file mode 100644
index 0000000..33367ea
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml
@@ -0,0 +1,39 @@
+@page
+@model IndexModel
+@{
+ ViewData["Title"] = "Home page";
+}
+
+
+@section Scripts
+{
+
+
+
+
+
+}
+
+ Salary range:
+
+
+
+
+
+
+
+
+
+
+
+
+ EmployeeId
+ Ssn
+ First Name
+ Last Name
+ Salary
+
+
+
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml.cs
new file mode 100644
index 0000000..964ada4
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Index.cshtml.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+///
+/// Index model
+///
+namespace ContosoHR.Pages
+{
+ public class IndexModel : PageModel
+ {
+ private readonly ILogger _logger;
+
+ public IndexModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml
new file mode 100644
index 0000000..46ba966
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml
@@ -0,0 +1,8 @@
+@page
+@model PrivacyModel
+@{
+ ViewData["Title"] = "Privacy Policy";
+}
+@ViewData["Title"]
+
+Use this page to detail your site's privacy policy.
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml.cs b/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml.cs
new file mode 100644
index 0000000..f4b19ac
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Privacy.cshtml.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+///
+/// Privacy model
+///
+namespace ContosoHR.Pages
+{
+ public class PrivacyModel : PageModel
+ {
+ private readonly ILogger _logger;
+
+ public PrivacyModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_Layout.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_Layout.cshtml
new file mode 100644
index 0000000..14c1561
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_Layout.cshtml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ @ViewData["Title"] - Contoso HR
+
+
+
+
+
+
+
+ @RenderBody()
+
+
+
+
+
+
+
+
+
+ @await RenderSectionAsync("Scripts", required: false)
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_ValidationScriptsPartial.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_ValidationScriptsPartial.cshtml
new file mode 100644
index 0000000..5a16d80
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/Shared/_ValidationScriptsPartial.cshtml
@@ -0,0 +1,2 @@
+
+
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/_ViewImports.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/_ViewImports.cshtml
new file mode 100644
index 0000000..bb0232f
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using ContosoHR
+@namespace ContosoHR.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/sovereignApplications/confidential/contosoHR/src/Pages/_ViewStart.cshtml b/sovereignApplications/confidential/contosoHR/src/Pages/_ViewStart.cshtml
new file mode 100644
index 0000000..a5f1004
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Pages/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Program.cs b/sovereignApplications/confidential/contosoHR/src/Program.cs
new file mode 100644
index 0000000..56247b7
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Program.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+///
+/// This is main entry for Costoso HR App to create the host builder and set configurations.
+///
+namespace ContosoHR
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Properties/ServiceDependencies/ContosoHR20210131163006 - Web Deploy/profile.arm.json b/sovereignApplications/confidential/contosoHR/src/Properties/ServiceDependencies/ContosoHR20210131163006 - Web Deploy/profile.arm.json
new file mode 100644
index 0000000..043dc6d
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Properties/ServiceDependencies/ContosoHR20210131163006 - Web Deploy/profile.arm.json
@@ -0,0 +1,113 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "_dependencyType": "appService.windows"
+ },
+ "parameters": {
+ "resourceGroupName": {
+ "type": "string",
+ "defaultValue": "aeenclavedemowus",
+ "metadata": {
+ "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
+ }
+ },
+ "resourceGroupLocation": {
+ "type": "string",
+ "defaultValue": "westus",
+ "metadata": {
+ "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
+ }
+ },
+ "resourceName": {
+ "type": "string",
+ "defaultValue": "ContosoHR20210131163006",
+ "metadata": {
+ "description": "Name of the main resource to be created by this template."
+ }
+ },
+ "resourceLocation": {
+ "type": "string",
+ "defaultValue": "[parameters('resourceGroupLocation')]",
+ "metadata": {
+ "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
+ }
+ }
+ },
+ "variables": {
+ "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
+ "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Resources/resourceGroups",
+ "name": "[parameters('resourceGroupName')]",
+ "location": "[parameters('resourceGroupLocation')]",
+ "apiVersion": "2019-10-01"
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
+ "resourceGroup": "[parameters('resourceGroupName')]",
+ "apiVersion": "2019-10-01",
+ "dependsOn": [
+ "[parameters('resourceGroupName')]"
+ ],
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [
+ {
+ "location": "[parameters('resourceLocation')]",
+ "name": "[parameters('resourceName')]",
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2015-08-01",
+ "tags": {
+ "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
+ },
+ "dependsOn": [
+ "[variables('appServicePlan_ResourceId')]"
+ ],
+ "kind": "app",
+ "properties": {
+ "name": "[parameters('resourceName')]",
+ "kind": "app",
+ "httpsOnly": true,
+ "reserved": false,
+ "serverFarmId": "[variables('appServicePlan_ResourceId')]",
+ "siteConfig": {
+ "metadata": [
+ {
+ "name": "CURRENT_STACK",
+ "value": "dotnetcore"
+ }
+ ]
+ }
+ },
+ "identity": {
+ "type": "SystemAssigned"
+ }
+ },
+ {
+ "location": "[parameters('resourceLocation')]",
+ "name": "[variables('appServicePlan_name')]",
+ "type": "Microsoft.Web/serverFarms",
+ "apiVersion": "2015-08-01",
+ "sku": {
+ "name": "S1",
+ "tier": "Standard",
+ "family": "S",
+ "size": "S1"
+ },
+ "properties": {
+ "name": "[variables('appServicePlan_name')]"
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sovereignApplications/confidential/contosoHR/src/Properties/launchSettings.json b/sovereignApplications/confidential/contosoHR/src/Properties/launchSettings.json
new file mode 100644
index 0000000..95e8683
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Properties/launchSettings.json
@@ -0,0 +1,21 @@
+{
+ "profiles": {
+ "Development": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Production": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://*:5001;http://*:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production"
+ }
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Startup.cs b/sovereignApplications/confidential/contosoHR/src/Startup.cs
new file mode 100644
index 0000000..c87e7a2
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Startup.cs
@@ -0,0 +1,137 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Azure.Core;
+using Azure.Identity;
+using ContosoHR.Models;
+using ContosoHR.Util;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Data.SqlClient;
+using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+
+///
+/// Startup app configurations.
+///
+namespace ContosoHR
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ IsDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
+
+ if (IsDevelopment)
+ {
+ // For dev-side debugging, ask dev to log in, and get config from appConfig file
+ ClientCredential = new InteractiveBrowserCredential();
+ ConnectionString = configuration.GetConnectionString("ContosoHRDatabase");
+ ConfidentialLedgerName = configuration.GetValue("ConfidentialLedgerName");
+ }
+ else
+ {
+ // Production credential picked up from VM environment and config from IMDS
+ ClientCredential = new DefaultAzureCredential();
+ string imdsUserObject = GetUserObjectFromImdsAsync().Result;
+ JsonNode configurationFromImds = JsonNode.Parse(imdsUserObject)!;
+ ConnectionString = (string)configurationFromImds["ContosoHRDatabase"];
+ ConfidentialLedgerName = (string)configurationFromImds["ConfidentialLedgerName"];
+ }
+
+ InitializeAzureKeyVaultProvider();
+ }
+
+ private bool IsDevelopment { get; set; }
+ private TokenCredential ClientCredential { get; set; }
+ private string ConnectionString { get; set; }
+ private string ConfidentialLedgerName { get; set; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(new ConfidentialLedgerLogger(ConfidentialLedgerName, "ContosoHrSqlLogs", ClientCredential));
+ services.AddDbContext(options =>
+ options.UseSqlServer(ConnectionString));
+ services.AddControllers();
+ services.AddRazorPages();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+ }
+
+ app.UseHttpsRedirection();
+ app.UseStaticFiles();
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ endpoints.MapRazorPages();
+ });
+ }
+
+ private static async Task GetUserObjectFromImdsAsync()
+ {
+ string connectionString = string.Empty;
+ using (var httpClient = new HttpClient())
+ {
+ // IMDS requires bypassing proxies.
+ WebProxy proxy = new WebProxy();
+ HttpClient.DefaultProxy = proxy;
+ httpClient.DefaultRequestHeaders.Add("Metadata", "True");
+ try
+ {
+ var b64connString = await httpClient.GetStringAsync("http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01&format=text");
+ var b64Bytes = Convert.FromBase64String(b64connString);
+ return Encoding.UTF8.GetString(b64Bytes);
+ }
+ catch (AggregateException ex)
+ {
+ // Handle response failures
+ Console.WriteLine("Request failed: " + ex.GetBaseException());
+ }
+ }
+ return connectionString;
+ }
+
+ private void InitializeAzureKeyVaultProvider()
+ {
+ // Initialize the Azure Key Vault provider
+ SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(ClientCredential);
+ // Register the Azure Key Vault provider
+ SqlConnection.RegisterColumnEncryptionKeyStoreProviders(
+ customProviders: new Dictionary(
+ capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
+ {
+ {
+ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider
+ }
+ }
+ );
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/Util/ConfidentialLedgerLogger.cs b/sovereignApplications/confidential/contosoHR/src/Util/ConfidentialLedgerLogger.cs
new file mode 100644
index 0000000..dd81a10
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/Util/ConfidentialLedgerLogger.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using Azure;
+using Azure.Core;
+using Azure.Security.ConfidentialLedger;
+using System;
+using System.Diagnostics;
+
+///
+/// Send log to confidential ledger.
+///
+namespace ContosoHR.Util
+{
+ public class ConfidentialLedgerLogger
+ {
+ readonly string ledgerUri;
+ readonly string collectionId;
+ readonly ConfidentialLedgerClient ledgerClient;
+
+ public ConfidentialLedgerLogger(string ledgerName, string collectionId, TokenCredential credential)
+ {
+ ledgerUri = $"https://{ledgerName}.confidential-ledger.azure.com";
+ this.collectionId = collectionId;
+ ledgerClient = new ConfidentialLedgerClient(new Uri(ledgerUri), credential);
+ }
+
+ public void Log(string message)
+ {
+ RequestContent content = RequestContent.Create(new { contents = message });
+ ledgerClient.PostLedgerEntryAsync(WaitUntil.Started, content, collectionId);
+ Debug.WriteLine($"Posted the following to {ledgerUri}: \n{message}");
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/appsettings.Development.json b/sovereignApplications/confidential/contosoHR/src/appsettings.Development.json
new file mode 100644
index 0000000..c9f0609
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/appsettings.Development.json
@@ -0,0 +1,14 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ "ConfidentialLedgerName": "LEDGERNAME",
+ "ConnectionStrings": {
+ "ContosoHRDatabase": "Data Source = SQLSERVERNAME.database.windows.net; Initial Catalog = DBNAME; Column Encryption Setting = Enabled;Attestation Protocol = AAS; Enclave Attestation Url = https://sharedeus.eus.attest.azure.net/attest/SgxEnclave; Authentication=Active Directory Interactive;"
+ }
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/appsettings.json b/sovereignApplications/confidential/contosoHR/src/appsettings.json
new file mode 100644
index 0000000..4502038
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ "ConnectionStrings": {
+ "ContosoHRDatabase": "Data Source = SQLSERVERNAME.database.windows.net; Initial Catalog = SQLSERVERDB; Column Encryption Setting = Enabled;Attestation Protocol = AAS; Enclave Attestation Url = https://sharedREGION.REGION.attest.azure.net/attest/SgxEnclave; Authentication=Active Directory Managed Identity;"
+ },
+ "ConfidentialLedgerName": "acl"
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/libman.json b/sovereignApplications/confidential/contosoHR/src/libman.json
new file mode 100644
index 0000000..07676fd
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/libman.json
@@ -0,0 +1,30 @@
+{
+ "version": "1.0",
+ "defaultProvider": "unpkg",
+ "libraries": [
+ {
+ "library": "bootstrap@4.3.1",
+ "destination": "wwwroot/lib/bootstrap/"
+ },
+ {
+ "library": "jquery@3.5.1",
+ "destination": "wwwroot/lib/jquery/"
+ },
+ {
+ "library": "jquery-validation@1.19.5",
+ "destination": "wwwroot/lib/jquery-validation/"
+ },
+ {
+ "library": "jquery-validation-unobtrusive@3.2.5",
+ "destination": "wwwroot/lib/jquery-validation-unobtrusive/"
+ },
+ {
+ "library": "jqueryui@1.11.1",
+ "destination": "wwwroot/lib/jqueryui/"
+ },
+ {
+ "library": "datatables.net@1.10.15",
+ "destination": "wwwroot/lib/datatables/"
+ }
+ ]
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/css/dataTables.bootstrap4.min.css b/sovereignApplications/confidential/contosoHR/src/wwwroot/css/dataTables.bootstrap4.min.css
new file mode 100644
index 0000000..2f2d67d
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/wwwroot/css/dataTables.bootstrap4.min.css
@@ -0,0 +1 @@
+:root{--dt-row-selected: 2, 117, 216;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{height:1em;width:1em;margin-top:-9px;display:inline-block;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable tr.dt-hasChild td.dt-control:before{content:"-";background-color:#d33333}table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting_asc_disabled,table.dataTable thead>tr>th.sorting_desc_disabled,table.dataTable thead>tr>td.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting_asc_disabled,table.dataTable thead>tr>td.sorting_desc_disabled{cursor:pointer;position:relative;padding-right:26px}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after{position:absolute;display:block;opacity:.125;right:10px;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:before{bottom:50%;content:"â–˛";content:"â–˛"/""}table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:after{top:50%;content:"â–Ľ";content:"â–Ľ"/""}table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting_asc_disabled:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dataTables_scrollBody>table.dataTable>thead>tr>th:before,div.dataTables_scrollBody>table.dataTable>thead>tr>th:after,div.dataTables_scrollBody>table.dataTable>thead>tr>td:before,div.dataTables_scrollBody>table.dataTable>thead>tr>td:after{display:none}div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:2px}div.dataTables_processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dataTables_processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(2, 117, 216);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dataTables_processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important;border-spacing:0}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.table-striped>tbody>tr:nth-of-type(2n+1){background-color:transparent}table.dataTable>tbody>tr{background-color:transparent}table.dataTable>tbody>tr.selected>*{box-shadow:inset 0 0 0 9999px rgb(2, 117, 216);box-shadow:inset 0 0 0 9999px rgb(var(--dt-row-selected));color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.dataTable>tbody>tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.dataTable.table-striped>tbody>tr.odd>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.05)}table.dataTable.table-striped>tbody>tr.odd.selected>*{box-shadow:inset 0 0 0 9999px rgba(2, 117, 216, 0.95);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95)}table.dataTable.table-hover>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.075)}table.dataTable.table-hover>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px rgba(2, 117, 216, 0.975);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975)}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:auto;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:.85em}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap;justify-content:flex-end}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody>table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody>table>thead .sorting:before,div.dataTables_scrollBody>table>thead .sorting_asc:before,div.dataTables_scrollBody>table>thead .sorting_desc:before,div.dataTables_scrollBody>table>thead .sorting:after,div.dataTables_scrollBody>table>thead .sorting_asc:after,div.dataTables_scrollBody>table>thead .sorting_desc:after{display:none}div.dataTables_scrollBody>table>tbody tr:first-child th,div.dataTables_scrollBody>table>tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}div.dataTables_wrapper div.dataTables_paginate ul.pagination{justify-content:center !important}}table.dataTable.table-sm>thead>tr>th:not(.sorting_disabled){padding-right:20px}table.table-bordered.dataTable{border-right-width:0}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:1px}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^=col-]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^=col-]:last-child{padding-right:0}
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/css/site.css b/sovereignApplications/confidential/contosoHR/src/wwwroot/css/site.css
new file mode 100644
index 0000000..042c9c7
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/wwwroot/css/site.css
@@ -0,0 +1,73 @@
+/* Copyright (c) Microsoft Corporation.
+ Licensed under the MIT License. */
+/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
+for details on configuring this project to bundle and minify static web assets. */
+
+a.navbar-brand {
+ white-space: normal;
+ text-align: center;
+ word-break: break-all;
+}
+
+/* Provide sufficient contrast against white background */
+a {
+ color: #0366d6;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+/* Sticky footer styles
+-------------------------------------------------- */
+html {
+ font-size: 14px;
+}
+@media (min-width: 768px) {
+ html {
+ font-size: 16px;
+ }
+}
+
+.border-top {
+ border-top: 1px solid #e5e5e5;
+}
+.border-bottom {
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.box-shadow {
+ box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
+}
+
+button.accept-policy {
+ font-size: 1rem;
+ line-height: inherit;
+}
+
+/* Sticky footer styles
+-------------------------------------------------- */
+html {
+ position: relative;
+ min-height: 100%;
+}
+
+body {
+ /* Margin bottom by footer height */
+ margin-bottom: 60px;
+}
+.footer {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ white-space: nowrap;
+ line-height: 60px; /* Vertically center the text there */
+}
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/favicon.ico b/sovereignApplications/confidential/contosoHR/src/wwwroot/favicon.ico
new file mode 100644
index 0000000..63e859b
Binary files /dev/null and b/sovereignApplications/confidential/contosoHR/src/wwwroot/favicon.ico differ
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/js/dataTables.bootstrap4.min.js b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/dataTables.bootstrap4.min.js
new file mode 100644
index 0000000..64ea61e
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/dataTables.bootstrap4.min.js
@@ -0,0 +1,4 @@
+/*! DataTables Bootstrap 4 integration
+ * ©2011-2017 SpryMedia Ltd - datatables.net/license
+ */
+!function(t){var n,o;"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return t(e,window,document)}):"object"==typeof exports?(n=require("jquery"),o=function(e,a){a.fn.dataTable||require("datatables.net")(e,a)},"undefined"!=typeof window?module.exports=function(e,a){return e=e||window,a=a||n(e),o(e,a),t(a,0,e.document)}:(o(window,n),module.exports=t(n,window,window.document))):t(jQuery,window,document)}(function(x,e,n,o){"use strict";var r=x.fn.dataTable;return x.extend(!0,r.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",renderer:"bootstrap"}),x.extend(r.ext.classes,{sWrapper:"dataTables_wrapper dt-bootstrap4",sFilterInput:"form-control form-control-sm",sLengthSelect:"custom-select custom-select-sm form-control form-control-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"}),r.ext.renderer.pageButton.bootstrap=function(i,e,d,a,l,c){function u(e,a){for(var t,n,o=function(e){e.preventDefault(),x(e.currentTarget).hasClass("disabled")||m.page()==e.data.action||m.page(e.data.action).draw("page")},r=0,s=a.length;r",{class:b.sPageButton+" "+f,id:0===d&&"string"==typeof t?i.sTableId+"_"+t:null}).append(x(document.createElement("a").setAttribute("href", n ? null : "#").setAttribute("aria-controls", i.sTableId).setAttribute("aria-disabled", n ? "true" : null).setAttribute("aria-label", w[t]).setAttribute("aria-role", "link").setAttribute("aria-current", f === "active" ? "page" : null).setAttribute("data-dt-idx", t).setAttribute("tabindex", i.iTabIndex).setAttribute("class", "page-link").innerHTML = p).appendTo(e),i.oApi._fnBindAction(n,{action:t},o))}}var p,f,t,m=new r.Api(i),b=i.oClasses,g=i.oLanguage.oPaginate,w=i.oLanguage.oAria.paginate||{};try{t=x(e).find(n.activeElement).data("dt-idx")}catch(e){}u(x(e).empty()[0].appendChild(document.createElement("ul").classList.add("pagination")), a),t!==o&&x(e).find("[data-dt-idx="+t+"]").trigger("focus")},r});
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/js/employeeDatatable.js b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/employeeDatatable.js
new file mode 100644
index 0000000..0060d1d
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/employeeDatatable.js
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+$(document).ready(function () {
+ var table = $("#employeeDatatable").DataTable({
+ "processing": true,
+ "serverSide": true,
+ "filter": true,
+ "ajax": {
+ "url": "/api/employee",
+ "type": "POST",
+ "datatype": "json"
+ },
+ "columnDefs": [
+ {
+ "targets": [0],
+ "visible": false,
+ "searchable": false
+ }
+ ],
+ "columns": [
+ { "data": "employeeId", "name": "EmployeeId", "autoWidth": true },
+ { "data": "ssn", "name": "Ssn", "autoWidth": true },
+ { "data": "firstName", "name": "FirstName", "autoWidth": true },
+ { "data": "lastName", "name": "LastName", "autoWidth": true },
+ { "data": "salary", "name": "Salary", render: $.fn.dataTable.render.number(',', '.',0), "autoWidth": true }
+ ]
+ });
+
+ $(function () {
+ $("#slider-range").slider({
+ range: true,
+ min: 0,
+ max: 100000,
+ values: [0, 100000],
+ slide: function (event, ui) {
+ $("#amount").val("$" + ui.values[0] + " - $" + ui.values[1]);
+ var from = ui.values[0];
+ var to = ui.values[1];
+ table
+ .column(4) //---> THIS IS ID OF THE Salary column
+ .search(from + ":" + to)
+ .draw();
+ }
+ });
+ $("#amount").val("$" + $("#slider-range").slider("values", 0) +
+ " - $" + $("#slider-range").slider("values", 1));
+ });
+});
diff --git a/sovereignApplications/confidential/contosoHR/src/wwwroot/js/site.js b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/site.js
new file mode 100644
index 0000000..f3f0fb0
--- /dev/null
+++ b/sovereignApplications/confidential/contosoHR/src/wwwroot/js/site.js
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
+// for details on configuring this project to bundle and minify static web assets.
+
+// Write your JavaScript code.
diff --git a/sovereignApplications/confidential/hrAppWorkload/README.md b/sovereignApplications/confidential/hrAppWorkload/README.md
new file mode 100644
index 0000000..b4ba268
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/README.md
@@ -0,0 +1,35 @@
+# Confidential Sample Application
+
+## Motivation and purpose
+
+To ensure and validate [Sovereign Landing Zone](https://github.com/Azure/sovereign-landing-zone) (SLZ) deployed infrastructure serves confidential needs of the customers' workloads, we have designed a Human Resources (HR) confidential sample application to:
+
+* Prevent Microsoft operators from accessing your data at rest, in transit, or in use, when configured as directed.
+* Prevent unauthorized access when the workloads are running with [Azure confidential computing resources](https://learn.microsoft.com/azure/confidential-computing/overview-azure-products).
+* Allow only customers with the proper access policies to access secret keys stored in protected managed enclaves. Microsoft personnel or anyone else can't access the secret keys.
+* Validate that applied SLZ policies work as expected; policies are applied and enforced, and policies are auditable on change.
+
+Azure confidential computing enhances the security posture of your applications by protecting data and code when in use that is when running and being processed in memory. This extra level of protection elevates the existing security posture in Azure by running applications in hardware-encrypted trusted execution environments.
+
+After completing this tutorial, you'll learn about common confidential use cases validated on an end-to-end sovereign application. Follow these steps in order to complete the tutorial.
+
+1. [Scenario and use cases](docs/01-scenario-usecases.md)
+2. [Architecture](docs/02-architecture.md)
+3. [Prerequisites](docs/03-prerequisites.md)
+4. [Deployment](docs/04-deployment.md)
+5. [Managing application](docs/05-application-management.md)
+6. [Clean up resources](docs/06-cleanup-resources.md)
+
+## Issues and FAQ
+
+Common known issues and FAQ are listed in our [Known Issues and FAQ](./docs/07-faq.md) page.
+
+If you can't find an answer in the known issues page, log issues to [GitHub issues](https://github.com/Azure/cloud-for-sovereignty-quickstarts/issues)
+
+## Trademarks
+
+This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
+trademarks or logos is subject to and must follow
+[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).
+Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
+Any use of third-party trademarks or logos are subject to those third-party's policies.
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/01-scenario-usecases.md b/sovereignApplications/confidential/hrAppWorkload/docs/01-scenario-usecases.md
new file mode 100644
index 0000000..5f5c734
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/01-scenario-usecases.md
@@ -0,0 +1,27 @@
+# Scenario and use cases
+
+## Scenario
+
+A company, working on behalf of a national government, has been growing fast and they need to keep track of the payroll and benefits for all the new people joining them. Their HR data must be protected at rest, in transit, and while in use from unauthorized viewing, access or modification.
+
+## Use cases
+
+The use cases for which the company is audited yearly by a government cybersecurity officer is listed in the [`Managing application`](05-application-management.md) section. We'll experiment a deep walkthrough of these use cases demonstrating the capabilities of Microsoft Cloud for Sovereignty.
+
+The personas and their corresponding roles are listed as follows:
+
+| **Persona** | **Description** |
+| - | - |
+| Partner Cloud Administrator | I can successfully deploy a confidential workload into confidential Management Groups |
+| Partner Cloud Administrator | I get blocked by policies when deploying a non-confidential workload into confidential Management Groups |
+| Customer Cloud Administrator | I can manage an HR app securely and confidentially via a web application |
+| Customer Cloud Administrator | I can perform basic operations {list all employees, get employee by id, create a new employee, update salary, delete employee} on an HR app and confidential data will be protected if I don’t have the proper rights |
+| HR Administrator | I can view encrypted data using Enable Always Encrypted (column encryption) and Enable secure enclaves options |
+| Customer Cloud Administrator | As a malicious SQL database owner, I will be denied access to employee confidential data |
+| Cloud Security Officer | I have access to an immutable ledger of transition history for audit and to help meet regulatory compliance |
+| Malicious VM Administrator | I will be denied access to application logs and confidential employee data |
+| Malicious VM Administrator | With the access to Hypervisor, I will be denied access to the VM, including application binaries and in-memory data |
+
+## Next step
+
+* Learn about [Architecture](02-architecture.md) of this application.
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/02-architecture.md b/sovereignApplications/confidential/hrAppWorkload/docs/02-architecture.md
new file mode 100644
index 0000000..4f0e66c
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/02-architecture.md
@@ -0,0 +1,26 @@
+# Application architecture
+
+The sovereign application is deployed in a confidential Sovereign Landing Zone environment, specifically in the *Confidential Corp* Management Group. To learn more about SLZ architecture and the Management Groups, see [Architecture of the Sovereign Landing Zone (SLZ) Preview](https://github.com/Azure/sovereign-landing-zone/blob/main/docs/02-Architecture.md).
+
+## Azure capabilities in application architecture
+
+The application architecture implements the following Azure capabilities on a three-tier architecture:
+
+1. **Sovereign Landing Zone** - [The Sovereign Landing Zone (SLZ)](https://github.com/Azure/sovereign-landing-zone/blob/main/docs/01-Overview.md) is the recommended framework to secure deployment of workloads.
+
+1. **Sensitive Data** - [Azure SQL DB - Always Encrypted with secure enclaves](https://docs.microsoft.com/azure/azure-sql/database/always-encrypted-with-secure-enclaves-landing). For a sovereign confidential `HR` database, the `SSN` and `Salary` columns are encrypted using the Column Master Key ([`CMK`](https://learn.microsoft.com/sql/relational-databases/security/encryption/configure-always-encrypted-keys-using-ssms?view=sql-server-ver16)). This capability is the data-tier in architecture diagram.
+
+1. **Sensitive Data Encryption Keys** - [**Azure Key Vault**](https://learn.microsoft.com/azure/key-vault/general/overview) or [**Managed HSM**](https://docs.microsoft.com/azure/key-vault/managed-hsm/overview): Azure Key Vault or Managed HSM is the service that provides secure key management and cryptographic operations for sensitive data and applications. Managed HSM uses Hardware Security Modules (HSMs) to protect and manage cryptographic keys and secrets. A FIPS 140-2 Level 3 validated HSM is used in this case for storing the [Always Encrypted Column Master Key](https://docs.microsoft.com/sql/relational-databases/security/encryption/create-and-store-column-master-keys-always-encrypted?view=sql-server-ver16) - or `CMK` for `HR` Database.
+
+1. **Sensitive Application Logic** - [Azure Confidential VM - with AMD SEV-SNP](https://learn.microsoft.com/azure/confidential-computing/virtual-machine-solutions-amd): Used for hosting an ASP.NET web app that queries the `HR` Azure SQL DB using the [ADO.NET driver](https://docs.microsoft.com/sql/connect/ado-net/microsoft-ado-net-sql-server?view=azuresqldb-current) and a Python application that uses the Azure Confidential Ledger [PyPi Python package](https://pypi.org/project/azure-confidentialledger) to persist Sensitive Logs generated on the web app (in this case, query history). This capability is the front-tier in architecture diagram.
+
+1. **Sensitive Application logs** - [Azure Confidential Ledger](https://docs.microsoft.com/azure/confidential-ledger/). This capability is an append-only, immutable ledger [CCF](https://microsoft.github.io/CCF/main/overview/what_is_ccf.html) for hosting Sensitive Logs.
+
+
+## Current architecture
+
+![Architecture-Private](../media/architecture-current.png)
+
+## Next step
+
+* Continue with [Prerequisites](03-prerequisites.md)
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/03-prerequisites.md b/sovereignApplications/confidential/hrAppWorkload/docs/03-prerequisites.md
new file mode 100644
index 0000000..2f9843f
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/03-prerequisites.md
@@ -0,0 +1,35 @@
+# Prerequisites
+
+## Required prerequisites
+
+1. **Deployment subscription** - Owner rights on an active Azure Subscription that is deployed within a Sovereign Landing Zone (SLZ) Management Group. See [SLZ install instructions](https://github.com/Azure/sovereign-landing-zone/blob/main/docs/03-Deployment-Overview.md) to deploy your SLZ. The SLZ has to be deployed with `allowConfidentialLocations` parameter having **eastus**, **westus**, **northeurope** and **westeurope** as values. Azure supports only these regions for confidential services used in this application (e.g. Azure Confidential Ledger)
+
+1. **Subscription creation** - Follow [these instructions](03.1-subscription.md) on how to create a subscription.
+
+1. **Encryption at host** - The Sovereign App uses Confidential Computing, which requires enabling the provider feature "encryptionAtHost" of the "Microsoft.Compute" resource provider.
+ * The deployment script will turn on this feature if it isn't already, but this can take some time, in our experience up to 20-30 minutes.
+ * The script will wait on this operation to complete, polling Azure at regular intervals to see if it is done.
+ * If you wish, you can manually enable this feature yourself at any point after creating the subscription; that would reduce the time you need to wait for the feature when executing the deployment below
+ * Documentation on how to do so is available in articles targetting the [Azure PowerShell module](https://learn.microsoft.com/azure/virtual-machines/windows/disks-enable-host-based-encryption-powershell) and the [Azure CLI](https://learn.microsoft.com/azure/virtual-machines/linux/disks-enable-host-based-encryption-cli). You need to run first command to register, and then run the second command which checks the registration state is Registered (takes a few minutes):
+ ``` powershell
+ Register-AzProviderFeature -FeatureName "EncryptionAtHost" -ProviderNamespace "Microsoft.Compute"
+
+ Get-AzProviderFeature -FeatureName "EncryptionAtHost" -ProviderNamespace "Microsoft.Compute"
+ ```
+
+1. **Administration VM with prerequisite software** - The deploying workstation needs access to the SLZ hub network and ability to resolve DNS names within the SLZ's Private DNS Zones. It also needs certain software components installed. This is most easily accomplished by running the deployment from a Virtual Machine within the SLZ's provided *-connectivity* subscription; we provide a [admin VM Bicep script](../../../nonConfidential/adminVM/README.md) you can deploy for exactly this purpose.
+
+
+## Optional prerequisites
+
+On the admin VM installed earlier, you may want to install your preferred editor to edit configuration parameters and inspect the code. Two good free options are:
+
+* [Visual Studio Code](https://code.visualstudio.com/)
+ * [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)
+ * [PowerShell extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell)
+* [Visual Studio 2022 Community Edition](https://visualstudio.microsoft.com/vs/community/)
+ * [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.visualstudiobicep)
+
+## Next step
+
+* Continue with [Deployment](04-deployment.md)
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/03.1-subscription.md b/sovereignApplications/confidential/hrAppWorkload/docs/03.1-subscription.md
new file mode 100644
index 0000000..61bef4c
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/03.1-subscription.md
@@ -0,0 +1,39 @@
+# Create or reuse a deployment subscription
+
+## Create a deployment subscription
+
+> [!NOTE]
+> If you have an existing subscription, skip to Step 3.
+> If you don't have a subscription follow below steps to create it using command shell or [create it using Azure Portal](https://learn.microsoft.com/azure/cost-management-billing/manage/create-subscription#create-a-subscription) under `Confidential Corp` management group of the SLZ:
+
+1. Open **PowerShell 7.x.** command shell and install **Azure Az PowerShell module 10.2.0**.
+
+ ``` powershell
+ Install-Module -Name Az -RequiredVersion 10.2.0 -AllowClobber -Force
+
+1. Create subscription and wait 1-3 minutes for subscription to be created
+
+ ``` powershell
+ New-AzSubscriptionAlias -AliasName "" -SubscriptionName "" -BillingScope "/providers/Microsoft.Billing/BillingAccounts//enrollmentAccounts/" -Workload "Production"
+ ```
+
+1. Verify that subscription was created and grab the subscription ID.
+
+ ``` powershell
+ Get-AzSubscription -SubscriptionName ""
+ ``
+
+1. Add the subscription to your SLZ confidential corp management group. See [image](../media/slz-confidential-corp-mg.png) to identify the id of the management group in Azure Portal:
+
+ ``` powershell
+ New-AzManagementGroupSubscription -GroupName -SubscriptionId
+ ```
+
+> [!NOTE]
+> - If you are deploying the sample sovereign app or confidential VM template, we recommend using the `Confidential Corp` management group of the SLZ.
+> - If you are deploying the lighthouse template, we recommend using the `Management` management group of the SLZ under `Platform` management group.
+
+## Next step
+
+* If you're deploying the sovereign app, continue with [prerequisites](03-prerequisites.md#required-prerequisites).
+* If you're deploying confidential VM template, continue with [deployment step 4](.../../../../../../workloadAccelerators/confidentialVirtualMachine/docs/cvmAccelerator.md#deployment-instructions).
\ No newline at end of file
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/04-deployment.md b/sovereignApplications/confidential/hrAppWorkload/docs/04-deployment.md
new file mode 100644
index 0000000..47b4d88
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/04-deployment.md
@@ -0,0 +1,57 @@
+# Deployment
+
+We have split deployment in three parts:
+
+1. [**Pre-deployment**](#pre-deployment) which consists of downloading the source code on the administration VM, authenticate to Azure, and updating required parameters.
+1. [**Deployment**](#deployment-1) with execution of the main script that deploys the app.
+1. [**Post-deployment**](#post-deployment) which verifies deployed resources.
+
+You're now connected to the administrative VM workstation. Unless otherwise specified, any subsequent steps should be executed within the administrative VM and **NOT** on your personal workstation.
+
+## Predeployment
+
+1. On the administrative VM, open Windows PowerShell as administrator and run the below commands.
+
+1. Clone the current [Cloud for Sovereignity Apps repository](https://github.com/Azure/cloud-for-sovereignty-quickstarts/). Note that you will be required to authenticate using your GitHub credentials. Follow instructions there by going to `https://github.com/login/device` on your local machine to authenticate your VM via a code.
+
+ ``` powershell
+ git clone https://github.com/Azure/cloud-for-sovereignty-quickstarts/
+ ```
+
+1. Change directory to cloud-for-sovereignty-quickstarts
+
+ ``` powershell
+ cd .\cloud-for-sovereignty-quickstarts\
+ ```
+
+1. Run the given commands, one at a time, to authenticate to Azure (note that due to different authentication contexts for the Az and SqlServer PowerShell modules, both of these commands trigger popup sign in prompts). For this authentication, use the deployment tenant credentials and **NOT** your GitHub credentials. is the [subscription created](03.1-subscription.md) under SLZ confidential corp management group in previous step.
+
+ ``` powershell
+ Connect-AzAccount -Subscription
+ Add-SqlAzureAuthenticationContext -Interactive
+ ```
+
+1. Navigate to `cd .\sovereignApplications\confidential\hrAppWorkload\scripts\parameters\` and open ``hrAppWorkload.parameters.json`` file in a text editor, for example VS Code by using `code .`. Update the parameters and save the file. Use table [Parameters](04.1-deployment-parameters.md) for assistance.
+
+## Deployment
+
+1. Change directory to `scripts` and run PowerShell Script following command, where *<DeploymentExecutionLocation>* is the region in which Azure Resource Manager executes the deployment script. This step isn't necessarily where the deployed resources reside and is governed by the parameter *parDeploymentLocation* from the parameters file. If *<DeploymentExecutionLocation>* is left out, the value defaults to "eastus":
+
+ ``` powershell
+ cd ..\
+ .\hrAppWorkload.ps1
+ ```
+
+1. A successful run should end with `>>> Database initialization complete.`
+
+## Post-deployment
+
+1. After the deployment, go to [Azure portal](https://portal.azure.com/) - {your-subscription}-Deployments to verify that deployment was successful. This [image](../media/deployment.png) shows a successful deployment of the sovereign application.
+
+1. Navigate to the resource group that was created (last one on the deployment details list) and notice that the [resources](../media/resources.png) that have been created.
+
+1. Navigate to the virtual machine and copy the private IP address that was allocated to the VM. You'll need [this IP](../media/vm.png) in the next step to navigate to the HR Web App UI.
+
+### Next step
+
+* Continue with [Managing application](05-application-management.md)
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/04.1-deployment-parameters.md b/sovereignApplications/confidential/hrAppWorkload/docs/04.1-deployment-parameters.md
new file mode 100644
index 0000000..2f8835c
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/04.1-deployment-parameters.md
@@ -0,0 +1,67 @@
+# Update required parameters
+
+Before beginning deployment of the application, update the deployment parameter values that should be used.
+
+1. In the current repository, navigate to `\sovereignApplications\confidential\hrAppWorkload\scripts\parameters\` folder.
+
+ ```powershell
+ cd .\sovereignApplications\confidential\hrAppWorkload\scripts\parameters\
+ ```
+
+1. Open your local `hrAppWorkload.parameters.json` file in a text editor, for example VS Code by using
+
+ ``` powershell
+ code .
+ ```
+
+1. We provide default values for majority of available parameters, but the ones with no given values (for example, value is "") must be set: `parDeploymentLocation`, `parHubNetworkResourceId`, `parAdminPasswordOrKey`, and `parSqlAdministratorLoginPassword`. Refer to the Required parameters table or to parameter definitions [in the bicep script](../scripts/hrAppWorkload.bicep) for allowed values. Per JSON syntax, strings are delimited by quotes, whereas numeric values and boolean values aren't.
+
+1. Navigate to your SLZ connectivity subscription -> Resource Groups -> choose the Resource Group, which isn't NetworkWatcherRG -> on the Resources page, select the virtual network resource and go to its Properties page to retrieve the `parHubNetworkResourceId` parameter value as shown in image [SLZ virtual network](../media/slz-virtual-network.png)
+
+## Adding parameters
+
+In case you want to override an available deployment parameter that isn't in the parameter file, just add a JSON fragment corresponding to the ones already there, for example:
+
+``` json
+ "parSqlAdministratorLogin": {
+ "value": "SqlAdmin"
+ },
+```
+
+## Parameter descriptions
+
+See the tables below for descriptions and accepted values for all parameters supported in the [hrAppWorkload.parameters.json](../scripts/parameters/hrAppWorkload.parameters.json) file. By default, only the base parameter set (the subset deemed most useful to customers) is included in the parameters file, but all are supported and can be added to it. Parameters marked with ``*`` are `required`.
+
+### Required parameters
+
+ | Parameter |Description | Examples | Default value |
+ |---------------------|---------------|------------------------|---------------|
+ | parDeploymentLocation* | Location of the deployment. Confidential Computing region is required for this application. See [Confidential Computing](https://azure.microsoft.com/explore/global-infrastructure/products-by-region/?products=kubernetes-service,azure-attestation,key-vault,azure-sql-database,confidential-ledger,virtual-machines®ions=all) for the latest information on availability. As of 9/01/2023 Azure Confidential Ledger and AMD CVM DCasv5-series are only supported in **eastus**, **westus**, **northeurope** and **westeurope**. | eastus, westus, northeurope, westeurope. | eastus |
+ | parHubNetworkResourceId* | Resource ID of the SLZ's hub virtual network from the *%slzprefix%-connectivity* subscription. See that Vnets Properties page in Azure portal, as illustrated in picture above. | /subscriptions/Sub-Id/resourceGroups/rg-aslz-hub-network-eastus/providers/Microsoft.Network/virtualNetworks/hub-aslz-eastus | "" |
+ | parAdminPasswordOrKey* | The password or public key (see option `parAuthenticationType`) for the Admin user created on Confidential Virtual Machine. If password, must fulfill password complexity requirements set by SLZ policy. | P4$$w0rd!!! | "" |
+ | parSqlAdministratorLoginPassword* | The password for the administrator user created on the SQL Server. Must fulfill password complexity requirements set by SLZ policy. | P4$$w0rd!!! | "" |
+ | parDeploymentPrefix* | Prefix added to some of the resources deployed. | 2-5 character string | "hrapp" |
+ | parVnetAddressPrefix* | IP address space of the virtual network created for this workload, in CIDR notation. Must be unique across all vnets peered against the SLZ hub virtual network as referenced above. For example, if SLZ's parHubNetworkAddressPrefix is 10.20.0.0/16, then this parameter can't be the same, it can be 10.21.0.0/16| 10.21.0.0/16 | |
+ | parAuthenticationType* | The authentication type of the VM. | Allowed values are sshPublicKey, password | "password" |
+
+
+### Optional parameters
+
+
+ | Parameter |Description | Examples | Default value |
+ |---------------------|---------------|------------------------|---------------|
+ | parTags | List of tags to be applied to some resources. | {'product': 'hrapp'} | {'product': 'hrapp'} |
+ | parLedgerName | The Azure Confidential Ledger Name. Deployed resource has a unique name suffix appended to prevent namespace clashes. | acl | {parDeploymentPrefix}-ledger-{unique-suffix} |
+ | parAttestationProvidersName | The Attestation Provider name. Deployed resource has a unique name suffix appended to prevent namespace clashes. | hrappatt | {parDeploymentPrefix}attp{unique-suffix} |
+ | parKeyVaultName | The Azure Key Vault name to hold the keys the SQL Server encryption and the Disk Encryption Set used for the VM disks. Deployed resource has a unique name suffix appended to prevent namespace clashes. | hrappkv | {parDeploymentPrefix}-kv |
+ | parVmName | The Virtual Machine name to be created. Note that deployed resource has a unique name suffix appended to prevent namespace clashes. | hrapp-vm | {parDeploymentPrefix}-vm-{unique-suffix} |
+ | parSqlAdministratorLogin | The Sql Administrator sign in name | hrapp-admin | hrapp-admin |
+ | parAdminUsername | The Admin User name for Virtual Machine | hrapp-admin | hrapp-admin |
+ | parVirtualNetworkName | The virtual network name | - | {parDeploymentPrefix}-vnet |
+ | parNetworkSecurityGroupName | The network security group used for virtual network | - | {parDeploymentPrefix}-nsg |
+ | parSqlServerName | The Sql Server name. Deployed resource has a unique name suffix appended to prevent namespace clashes. | - | {parDeploymentPrefix}-sql-{unique-suffix} |
+ | parDatabaseName | The Sql database| - | {parDeploymentPrefix}-db |
+
+## Next step
+
+[Deploy the application](04-deployment.md#deployment-1)
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/05-application-management.md b/sovereignApplications/confidential/hrAppWorkload/docs/05-application-management.md
new file mode 100644
index 0000000..59b27f5
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/05-application-management.md
@@ -0,0 +1,69 @@
+# Managing application
+
+At this point, [HR application architecture](../media/architecture-current.png) was deployed in your Sovereign Landing Zone.
+Now, let's go over some of the scenarios of managing the application.
+
+1. Connect to ContosoHR Web App to perform basic operations on HR app:
+
+ * Connect to your admin VM and open a browser.
+ * Navigate to https://{VM-IP-address}:5001. On first run, you see a TLS/SSL warning, telling you that the connection may not be secure. This warning is to be expected and is because we're using a self-signed certificate for the ContosoHR service. Select through this warning; the connection is assuredly secure despite it (though in the general case, on the public Internet, you would do right to be *cautious if such a warning shows up!)
+ * You should see the [Employee List](../media/contosoHR-list.png)
+ * The SSN and Salary columns on the SQL Server has been encrypted with a Column Encryption Key (CEK) using [SQL Server Always Encrypted](https://learn.microsoft.com/sql/relational-databases/security/encryption/configure-always-encrypted-keys-using-ssms?view=sql-server-ver16). That CEK has in turn been encrypted by a Column Master Key (CMK) which is stored on a deployed Azure Key Vault entirely separate from the SQL Server. This design ensures that the encryption keys and encrypted sensitive data are kept separate, such that even a malicious SQL Server administrator is unable to decrypt the latter.
+ * The Confidential VM connects to the SQL Server using a service principal identity; that identity has read access to the Key Vault on which the CMK is stored. At runtime, the SQL driver on the Confidential VM retrieves the CMK, uses it to decrypt the CEK, and finally, uses the CEK to decrypt the encrypted data that is returned from the SQL server.
+ * For the sake of ease-of-illustration, the columns FirstName and LastName haven't been encrypted. The values can be retrieved in plaintext by a normal SQL query, by a user with sufficient querying privileges.
+ * Next you can manage the list of employees such as filter by their salary range, search by any of the columns or sort by columns.
+ * You can also navigate to Employees tab to create new employees, [edit employee](../media/contosoHR-employee-edit.png) or delete existing.
+
+1. As an HR Admin authorized user using `Enable Always Encrypted (column encryption)` and `Enable secure enclaves` options, I can view encrypted data
+ * Stay on the same admin VM and OpenSQL Server Management Studio (SSMS).
+ * Connect with your account that ran the deployment and authenticate with tenant credentials, and choose the database. Use an Azure attestation provider to connect to SGX enclaves on SQL Server Always Encrypted, for example, https://sharedwus.wus.attest.azure.net. Follow these steps shown in images: [SQL Connect 1](../media/connect-to-sql-1.png), [SQL Connect 2](../media/connect-to-sql-2.png), [SQL Connect 3](../media/connect-to-sql-3.png), [SQL Choose DB ](../media/sql-server-choose-db.png)
+
+ > [!NOTE]
+ > In a real-world scenario, users have to create their [attestation provider with signed policies](https://learn.microsoft.com/azure/attestation/quickstart-portal#create-and-configure-the-provider-with-signed-policies)
+
+ * Once you connected, expand Databases - {your-db} - HR.Employees table. Right select on the table and Select top 1000 Rows
+ * You should be able to see clear text for SSN and Salary as shown in the image [SQL Query Plain](../media/query-sql-plain.png)
+
+1. As a malicious SQL database admin/owner, I can't see employee confidential data if I don't use `Enable Always Encrypted (column encryption)` and `Enable secure enclaves` options with the correct enclave attestation service Url.
+ * Connect with the SqlAdministratorLogin: `hrapp-admin` and the password that you have set in the parameter file, and uncheck `Enable Always Encrypted (column encryption)` and `Enable secure enclaves` in the Always Encrypted tab, as shown in the images [SQL Connect 5](../media/connect-to-sql-5.png), [SQL Connect 4](../media/connect-to-sql-4.png)
+
+ * Once you connected, expand Databases - {your-db} - HR.Employees table. Right select on the table and Select top 1000 Rows
+ * You should see cipher text for SSN and Salary as shown in the image [SQL Query Cipher](../media/query-sql-cipher.png)
+
+1. As HR Admin, I have access to an immutable ledger of transition history for audit and to help meet regulatory compliance. Logs are streamed to Confidential Ledger.
+
+ * To retrieve your logs from the Azure Confidential Ledger, use the [Confidential Ledger Client](../../contosoHR/ConfidentialLedgerClient/README.md) provided for this purpose.
+ * To run the client, you need to connect to admin VM, then you need to build it, as documented in the link earlier. Then step into the Confidential Ledger Client directory and execute the following commands:
+ `dotnet run %LEDGER_NAME%`, where `%LEDGER_NAME%` is the resource name of your ledger as seen in the Azure portal, for example, `hrapp-acl-pwm5h3hwyh3ra`.
+ * You may need to authenticate to Azure in a pop-up window; if so, use the deployment tenant credentials, **NOT** your Microsoft credentials.
+ * If all goes well, the client retrieves the ledger entries and output to the console.
+
+1. Deploy a nonconfidential service (Azure Storage) and get rejected by policies:
+
+ * In the same Windows PowerShell where you deployed your application (assuming you are in `cloud-for-sovereignty-quickstarts` folder), run the following commands, one by one:
+
+ ``` powershell
+ cd .\sovereignApplications\nonConfidential\storageAccount\scripts
+ Connect-AzAccount -Subscription
+ $groupName = "rg-test"
+ $location = ""
+ New-AzResourceGroup -Name $groupName -Location $location
+ # The below command should fail with a code indicating RequestDisallowedByPolicy
+ New-AzResourceGroupDeployment -Name "PolicyFailingStorageAccountDeployment" -ResourceGroupName $groupName -TemplateFile "template.storageaccountcmk.bicep"
+ ```
+
+ * It's expected to get a [policy violation error](../media/policy-violation.png), which is enforced by SLZ policies of not allowing nonconfidential services into subscriptions located in confidential management groups.
+ * Clean up this test by removing the test resource group:
+
+ ``` powershell
+ Remove-AzResourceGroup -Name $groupName
+ ```
+
+1. Explore the SLZ Dashboard compliance for your subscription, which has the sovereign workload application deployed
+ * Navigate to your SLZ Dashboard. The Sovereign Landing Zone Preview Compliance Dashboard can be accessed in the Dashboards section of the Azure portal. The naming convention follows the pattern ${parDeploymentPrefix}-Sovereign-Landing-Zone-Dashboard-Preview-${parDeploymentLocation}, utilizing the parameters provided during SLZ deployment.
+ * Notice that the `Overall resources compliance rate` and `Overall confidential compliance score` have changed as you have deployed a confidential application, which is compliant with `SLZ Confidential Policies`, which can be found under `Confidential Corp` confidential policies. See [image](../media/slz-compliance-dashboard.png) for details.
+
+
+## Next step
+
+* Continue with [Clean up resources](06-cleanup-resources.md)
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/06-cleanup-resources.md b/sovereignApplications/confidential/hrAppWorkload/docs/06-cleanup-resources.md
new file mode 100644
index 0000000..65fdd10
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/06-cleanup-resources.md
@@ -0,0 +1,14 @@
+# Clean up resources
+
+1. Delete the resource group created by this application by running the following command in the Windows PowerShell:
+
+``` powershell
+ Remove-AzResourceGroup -Name -rg -Force
+```
+
+1. Delete the subscription created for this deployment:
+
+``` powershell
+ Install-Module Az.Subscription
+ Disable-AzSubscription -Id
+```
\ No newline at end of file
diff --git a/sovereignApplications/confidential/hrAppWorkload/docs/07-faq.md b/sovereignApplications/confidential/hrAppWorkload/docs/07-faq.md
new file mode 100644
index 0000000..a800583
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/docs/07-faq.md
@@ -0,0 +1,22 @@
+# HR sovereign application - Frequently Asked Questions
+
+This document answers the most common questions related to the HR Sovereign application.
+
+1. Deployment fails with error `Code:CreateDatabaseAttemptedWithRotationInProgress`. See image for exact [error message](../media/database-tde-error.png), you need to wait and retry the same deployment script.
+
+ * **Reason**: This error message indicates that the database creation operation can't be performed at this time due to ongoing TDE key rotation on the server. You may need to wait until the TDE key rotation process is complete before attempting to create the database again.
+ * **Resolution**: You need to wait and retry the same deployment script. Rerun the script `.\hrAppWorkload.ps1` from `cloud-for-sovereignty-quickstarts\sovereignApplications\confidential\hrAppWorkload` folder.
+
+
+1. Deployment fails with error `ParameterBindingException`. See image for exact [error message](../media/parameter-binding-error.png)
+
+ * **Reason**: This error message indicates that your Azure authentication has expired, and your Azure SQL authentication
+ * **Resolution**: Rerun the scripts to authenticate from [predeployment](../docs/04-deployment.md#pre-deployment) and then continue with [deployment](../docs/04-deployment.md#deployment-1) steps.
+
+ ``` Powershell
+ Connect-AzAccount -Subscription
+ Add-SqlAzureAuthenticationContext -Interactive
+ ```
+
+To report issues or get support, submit a ticket through [GitHub Issues](https://github.com/Azure/cloud-for-sovereignty-quickstarts/issues)
+
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/architecture-current.png b/sovereignApplications/confidential/hrAppWorkload/media/architecture-current.png
new file mode 100644
index 0000000..dcb27ab
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/architecture-current.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM1.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM1.png
new file mode 100644
index 0000000..f6340b5
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM1.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM2.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM2.png
new file mode 100644
index 0000000..92d73c8
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM2.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-1.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-1.png
new file mode 100644
index 0000000..04c98a2
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-1.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-2.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-2.png
new file mode 100644
index 0000000..354e343
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-2.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-3.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-3.png
new file mode 100644
index 0000000..00d2c25
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-3.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-4.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-4.png
new file mode 100644
index 0000000..b48f0d1
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-4.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-5.png b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-5.png
new file mode 100644
index 0000000..78cb9db
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/connect-to-sql-5.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-employee-edit.png b/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-employee-edit.png
new file mode 100644
index 0000000..ee3fdbc
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-employee-edit.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-list.png b/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-list.png
new file mode 100644
index 0000000..087980a
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/contosoHR-list.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/database-tde-error.png b/sovereignApplications/confidential/hrAppWorkload/media/database-tde-error.png
new file mode 100644
index 0000000..a04c0e3
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/database-tde-error.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/deployment.png b/sovereignApplications/confidential/hrAppWorkload/media/deployment.png
new file mode 100644
index 0000000..4510a74
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/deployment.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/parameter-binding-error.png b/sovereignApplications/confidential/hrAppWorkload/media/parameter-binding-error.png
new file mode 100644
index 0000000..51e846d
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/parameter-binding-error.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/policy-violation.png b/sovereignApplications/confidential/hrAppWorkload/media/policy-violation.png
new file mode 100644
index 0000000..68593d1
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/policy-violation.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/query-sql-cipher.png b/sovereignApplications/confidential/hrAppWorkload/media/query-sql-cipher.png
new file mode 100644
index 0000000..0d8263c
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/query-sql-cipher.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/query-sql-plain.png b/sovereignApplications/confidential/hrAppWorkload/media/query-sql-plain.png
new file mode 100644
index 0000000..1ba247f
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/query-sql-plain.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/resources.png b/sovereignApplications/confidential/hrAppWorkload/media/resources.png
new file mode 100644
index 0000000..a9495e9
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/resources.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/slz-compliance-dashboard.png b/sovereignApplications/confidential/hrAppWorkload/media/slz-compliance-dashboard.png
new file mode 100644
index 0000000..c27c9fa
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/slz-compliance-dashboard.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/slz-confidential-corp-mg.png b/sovereignApplications/confidential/hrAppWorkload/media/slz-confidential-corp-mg.png
new file mode 100644
index 0000000..966f7d7
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/slz-confidential-corp-mg.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/slz-virtual-network.png b/sovereignApplications/confidential/hrAppWorkload/media/slz-virtual-network.png
new file mode 100644
index 0000000..3521f45
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/slz-virtual-network.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/sql-server-choose-db.png b/sovereignApplications/confidential/hrAppWorkload/media/sql-server-choose-db.png
new file mode 100644
index 0000000..50a4f11
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/sql-server-choose-db.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/media/vm.png b/sovereignApplications/confidential/hrAppWorkload/media/vm.png
new file mode 100644
index 0000000..5b1fc6d
Binary files /dev/null and b/sovereignApplications/confidential/hrAppWorkload/media/vm.png differ
diff --git a/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppInfra.bicep b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppInfra.bicep
new file mode 100644
index 0000000..2053f65
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppInfra.bicep
@@ -0,0 +1,362 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the HR App Azure infrastructures.
+DESCRIPTION: This module will create a deployment which will create user assigned managed identity, attestation provider, virtual networks, etc..
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Timestamp used to date subdeployments.')
+param parTimestamp string
+
+@description('Location for all resources.')
+@allowed([
+ 'eastus'
+ 'westus'
+ 'northeurope'
+ 'westeurope'
+])
+param parDeploymentLocation string
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string
+
+@description('Object ID for the Azure AD user who is set as Key Vault Administrator on Key Vault or Managed HSM.')
+param parKeyVaultOrMhsmAdminObjectId string
+
+@description('Object ID for the Azure AD user who is set as Attestation Contributor.')
+param parAttestationContributorObjectId string
+
+@description('Attestation providers name')
+param parAttestationProvidersName string
+
+@description('Key vault name')
+param parKeyVaultName string
+
+@description('Name of the virtual network')
+param parVirtualNetworkName string
+
+@description('Name of the network security group')
+param parNetworkSecurityGroupName string
+
+@description('Resource tags')
+param parTags object
+
+@description('The key size in bits. For example: 2048, 3072, or 4096 for RSA.')
+@allowed([
+ 2048
+ 3072
+ 4096
+])
+param parKeySize int = 2048
+
+@description('The type of key to create')
+@allowed([
+ 'EC'
+ 'EC-HSM'
+ 'RSA'
+ 'RSA-HSM'
+])
+param parKty string = 'RSA'
+
+@description('Name of the key created for use with SQL Server encryption.')
+param parSqlServerKeyName string = 'SqlServerEncryptionKey'
+
+@description('Name of the key created for use with VM Disk Encryption Set.')
+param parVmDiskEncryptionKeyName string = 'VmDiskEncryptionKey'
+
+@description('Name of the key created as Column Master Key for use with SQL Server Always Encrypted.')
+param parSqlColumnMasterKeyName string = 'SqlServerColumnMasterKey'
+
+@description('Expiry date in seconds since 1970-01-01T00:00:00Z.')
+param parKeyExpirationDate int
+
+@description('IP address space of the VNET created for this workload, in CIDR notation.')
+param parVnetAddressPrefix string
+
+@description('IP address space of the VNET child subnet created for this workload, in CIDR notation.')
+param parSubnetAddressPrefix string
+
+@description('Resource ID of the SLZ\'s hub vnet in the -connectivity subscription.')
+param parHubNetworkResourceId string
+
+var varSubnetName = 'subnet'
+var varDiskEncryptionSetName = 'diskEncryptionSetVM'
+
+var varRoleIdMapping = {
+ KeyVaultCryptoOfficer: '14b46e9e-c2b7-41b4-b07b-48a6ebf60603'
+ KeyVaultAdministrator: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
+ AttestationContributor: 'bbf86eb8-f7b4-4cce-96e4-18cddf81d86e'
+}
+
+// These computed values are used by the Peering module to connect hub->spoke vnet
+var varHubNetworkSubscription = split(parHubNetworkResourceId, '/')[2]
+var varHubNetworkResourceGroup = split(parHubNetworkResourceId, '/')[4]
+var varHubNetworkName = split(parHubNetworkResourceId, '/')[8]
+
+// The built-in Key Vault Administrator role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator
+resource resKeyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: varRoleIdMapping.KeyVaultAdministrator
+}
+
+// The built-in Attestation Contributor role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#attestation-contributor
+resource resAttestationContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: varRoleIdMapping.AttestationContributor
+}
+
+resource resManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
+ name: parManagedIdentityName
+}
+
+resource resAttestationProvider 'Microsoft.Attestation/attestationProviders@2021-06-01' = {
+ name: parAttestationProvidersName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {}
+}
+
+// Assign the RBAC Attributon Contributor role to the newly minted Attestation Provider.
+resource resAttestationContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: resAttestationProvider
+ name: guid(resAttestationProvider.id, parAttestationContributorObjectId, varRoleIdMapping.AttestationContributor)
+ properties: {
+ roleDefinitionId: resAttestationContributorRoleDefinition.id
+ principalId: parAttestationContributorObjectId
+ }
+}
+
+// Attestation Provider Private Endpoint
+var varAttestationPrivateEndpointName = 'endpoint-${parAttestationProvidersName}'
+resource resAttestationProviderEndpoint 'Microsoft.Network/privateEndpoints@2022-07-01' = {
+ name: varAttestationPrivateEndpointName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ privateLinkServiceConnections: [
+ {
+ name: varAttestationPrivateEndpointName
+ properties: {
+ privateLinkServiceId: resAttestationProvider.id
+ groupIds: [
+ 'standard'
+ ]
+ }
+ }
+ ]
+ subnet: {
+ id: resVirtualNetwork.properties.subnets[0].id
+ }
+ customNetworkInterfaceName: 'nic-${varAttestationPrivateEndpointName}'
+ }
+}
+
+// Register Private Endpoint with Private DNS zone to allow DNS resolution on SLZ VNets
+resource resAttestationProviderPrivateEndpointZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-07-01' = {
+ name: 'default'
+ parent: resAttestationProviderEndpoint
+ properties: {
+ privateDnsZoneConfigs: [
+ {
+ name: 'privatelink-attest-azure-net'
+ properties: {
+ privateDnsZoneId: modHubNetworkPrivateDnsZones.outputs.outAttestationDnsZoneId
+ }
+ }
+ ]
+ }
+}
+
+resource resKeyvault 'Microsoft.KeyVault/vaults@2022-07-01' = {
+ name: parKeyVaultName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ enableRbacAuthorization: true
+ enableSoftDelete: true
+ enablePurgeProtection: true
+ enabledForDeployment: true
+ enabledForDiskEncryption: true
+ enabledForTemplateDeployment: true
+ tenantId: tenant().tenantId
+ accessPolicies: []
+ sku: {
+ family: 'A'
+ name: 'standard'
+ }
+ networkAcls: {
+ defaultAction: 'Allow'
+ bypass: 'AzureServices'
+ }
+ }
+}
+
+resource resVmDiskEncryptionKey 'Microsoft.KeyVault/vaults/keys@2022-07-01' = {
+ parent: resKeyvault
+ name: parVmDiskEncryptionKeyName
+ tags: parTags
+ properties: {
+ kty: parKty
+ keySize: parKeySize
+ attributes: {
+ exp: parKeyExpirationDate
+ }
+ }
+ dependsOn: [
+ resAdminForManagedIdentity
+ ]
+}
+
+resource resSqlServerKey 'Microsoft.KeyVault/vaults/keys@2022-07-01' = {
+ parent: resKeyvault
+ name: parSqlServerKeyName
+ tags: parTags
+ properties: {
+ kty: parKty
+ keySize: parKeySize
+ attributes: {
+ exp: parKeyExpirationDate
+ }
+ }
+ dependsOn: [
+ resAdminForManagedIdentity
+ ]
+}
+
+resource resSqlColumnMasterKey 'Microsoft.KeyVault/vaults/keys@2022-07-01' = {
+ parent: resKeyvault
+ name: parSqlColumnMasterKeyName
+ tags: parTags
+ properties: {
+ kty: parKty
+ keySize: parKeySize
+ attributes: {
+ exp: parKeyExpirationDate
+ }
+ }
+ dependsOn: [
+ resAdminForManagedIdentity
+ ]
+}
+
+resource resDiskEncryptionSet 'Microsoft.Compute/diskEncryptionSets@2022-03-02' = {
+ name: varDiskEncryptionSetName
+ location: parDeploymentLocation
+ tags: parTags
+ identity: {
+ type: 'UserAssigned'
+ userAssignedIdentities: {
+ '${resManagedIdentity.id}': {}
+ }
+ }
+ properties: {
+ activeKey: {
+ sourceVault: {
+ id: resKeyvault.id
+ }
+ keyUrl: resVmDiskEncryptionKey.properties.keyUriWithVersion
+ }
+ encryptionType: 'EncryptionAtRestWithPlatformAndCustomerKeys'
+ rotationToLatestKeyVersionEnabled: true
+ }
+ dependsOn: [
+ resAdminForManagedIdentity
+ ]
+}
+
+resource resAdminForManagedIdentity 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: resKeyvault
+ name: guid(resKeyvault.id, resManagedIdentity.id, varRoleIdMapping.KeyVaultAdministrator)
+ properties: {
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalId: resManagedIdentity.properties.principalId
+ principalType: 'ServicePrincipal'
+ }
+}
+
+resource resKeyVaultAdmin 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: resKeyvault
+ name: guid(resKeyvault.id, parKeyVaultOrMhsmAdminObjectId, varRoleIdMapping.KeyVaultAdministrator)
+ properties: {
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalId: parKeyVaultOrMhsmAdminObjectId
+ }
+}
+
+resource resNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-07-01' = {
+ name: parNetworkSecurityGroupName
+ location: parDeploymentLocation
+}
+
+// Private DNS Zones created in the SLZ -connectivity subscription, such that the
+// SQL Server and Attestation Provider Private Endpoints can later register with these
+// and be adressable on the SLZ VNet
+module modHubNetworkPrivateDnsZones '../../../../common/modules/module.privatedns.bicep' = {
+ scope: resourceGroup(varHubNetworkSubscription, varHubNetworkResourceGroup)
+ name: 'ContosoHR.Common.PrivateDnsZones-${parTimestamp}'
+ params: {
+ parHubNetworkId: parHubNetworkResourceId
+ parSpokeNetworkId: resVirtualNetwork.id
+ }
+}
+
+resource resVirtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = {
+ name: parVirtualNetworkName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ addressSpace: {
+ addressPrefixes: [
+ parVnetAddressPrefix
+ ]
+ }
+ subnets: [
+ {
+ name: varSubnetName
+ properties: {
+ addressPrefix: parSubnetAddressPrefix
+ networkSecurityGroup: {
+ id: resNetworkSecurityGroup.id
+ }
+ }
+ }
+ ]
+ }
+}
+
+// Peering from new VNET to the hub network in SLZ -connectivity subscription
+module modPeeringToHub '../../../../common/modules/module.peering.bicep' = {
+ name: 'ContosoHR.Common.Peering.Outgoing-${parTimestamp}'
+ params: {
+ parHomeNetworkName: resVirtualNetwork.name
+ parRemoteNetworkId: parHubNetworkResourceId
+ parAllowGatewayTransit: false
+ parUseRemoteGateways: true
+ }
+}
+
+// Inverse peering from hub network in SLZ -connectivity subscription to the new VNET
+module modPeeringFromHub '../../../../common/modules/module.peering.bicep' = {
+ name: 'ContosoHR.Common.Peering.Outgoing-${parTimestamp}'
+ scope: resourceGroup(varHubNetworkSubscription, varHubNetworkResourceGroup)
+ params: {
+ parHomeNetworkName: varHubNetworkName
+ parRemoteNetworkId: resVirtualNetwork.id
+ parAllowGatewayTransit: true
+ parUseRemoteGateways: false
+ }
+}
+
+output outDiskEncryptionSetId string = resDiskEncryptionSet.id
+output outSqlkeyId string = resSqlServerKey.id
+output outSqlkeyName string = resSqlServerKey.name
+output outSqlCmkUrl string = resSqlColumnMasterKey.properties.keyUriWithVersion
+output outSqlkeyUrl string = resSqlServerKey.properties.keyUriWithVersion
+output outVmkeyId string = resVmDiskEncryptionKey.id
+output outVmkeyUrl string = resVmDiskEncryptionKey.properties.keyUriWithVersion
+output outVirtualNetworkName string = resVirtualNetwork.name
+output outVirtualNetworkId string = resVirtualNetwork.id
+output outAttestationDnsZoneId string = modHubNetworkPrivateDnsZones.outputs.outAttestationDnsZoneId
+output outSqlServerDnsZoneId string = modHubNetworkPrivateDnsZones.outputs.outSqlServerDnsZoneId
+output outAttestationProviderName string = resAttestationProvider.name
+output outAttestationProviderAttestUri string = resAttestationProvider.properties.attestUri
diff --git a/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.bicep b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.bicep
new file mode 100644
index 0000000..57a524c
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.bicep
@@ -0,0 +1,225 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the HR App workload bicep scripts.
+DESCRIPTION: This module will create a deployment which will create HR App workload with its azure infrastructures.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'subscription'
+
+@description('The prefix that will be added to all resources created by this deployment.')
+@minLength(2)
+@maxLength(5)
+param parDeploymentPrefix string
+
+@description('Deployment location for all resources.')
+@allowed([
+ 'eastus'
+ 'westus'
+ 'northeurope'
+ 'westeurope'
+])
+param parDeploymentLocation string
+
+@description('Timestamp used to date subdeployments.')
+param parTimestamp string = utcNow()
+
+@description('The Resource Group in which this sample workload will be deployed.')
+param parResourceGroupName string = '${parDeploymentPrefix}-rg'
+
+@description('Object ID of the user executing this deployment (used for RBAC assignments supporting later scripted steps).')
+param parDeployingUserObjectId string
+
+@description('User Principal Name of the user user executing this deployment (used to set SQL server admin account).')
+param parDeployingUserUpn string
+
+@maxLength(10)
+@description('The Azure Confidential Ledger Name, e.g., prefix-acl. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes. Note also that due to name length restrictions on ledger resources (24 characters) and due to said suffix that is added, this parameter has a maxlength restriction of 10 characters.')
+param parLedgerName string = '${parDeploymentPrefix}-acl'
+
+@maxLength(10)
+@description('The Attestation Provider name, e.g., prefixattp. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parAttestationProvidersName string = '${parDeploymentPrefix}attp'
+
+@description('Name of Key Vault holding keys for SQL Server encryption, Always Encrypted, and disk encryption of VM disks, e.g., myprefix-kv. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parKeyVaultName string = '${parDeploymentPrefix}-kv'
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string = '${parDeploymentPrefix}-id'
+
+@description('Key expiry date in seconds since 1970-01-01T00:00:00Z. Defaults to December 31, 2024.')
+param parKeyExpirationDate int = dateTimeToEpoch('20241231T235959Z')
+
+@description('Resource ID of the SLZ\'s hub vnet in the -connectivity subscription.')
+param parHubNetworkResourceId string
+
+@description('IP address space of the VNET created for this workload, in CIDR notation.')
+param parVnetAddressPrefix string
+
+@description('Name of the Confidential Virtual Machine to be created, e.g., myprefix-vm. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parVmName string = '${parDeploymentPrefix}-vm'
+
+@description('OS image for the virtual Machine')
+param parOsImageName string = 'Ubuntu 22.04 LTS Gen 2'
+
+@description('The administrator username of the SQL logical server')
+param parSqlAdministratorLogin string = 'hrapp-admin'
+
+@description('The administrator password of the SQL logical server.')
+@secure()
+param parSqlAdministratorLoginPassword string
+
+@description('Username for the virtual machine.')
+param parAdminUsername string = 'hrapp-admin'
+
+@description('Password for the virtual machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
+@secure()
+param parAdminPasswordOrKey string
+
+@description('Type of authentication to use on the virtual machine. SSH Public key is recommended.')
+@allowed([
+ 'sshPublicKey'
+ 'password'
+])
+param parAuthenticationType string = 'password'
+
+@description('The virtual network name for this deployment, e.g., myprefix-vnet.')
+param parVirtualNetworkName string = '${parDeploymentPrefix}-vnet'
+
+@description('The network security group used for virtual network, e.g., myprefix-nsg.')
+param parNetworkSecurityGroupName string = '${parDeploymentPrefix}-nsg'
+
+@description('List of tags to be applied to resources.')
+param parTags object = { product: 'hrapp' }
+
+@description('The SQL Server name, e.g., myprefix-sql. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parSqlServerName string = '${parDeploymentPrefix}-sql'
+
+@description('The SQL Database name.')
+param parSqlDatabaseName string = '${parDeploymentPrefix}-db'
+
+@description('Value that populates CVM OSProfile/customData field (e.g., a cloud-init configuration).')
+param parVmCustomData string = loadFileAsBase64('../../contosoHR/config/cloud-init.yaml')
+
+@description('Suffix used to add unique identity to globally namespaced resources.')
+var varUniqueSuffix = uniqueString(resResourceGroup.id, parDeploymentLocation)
+
+@description('Key vault name with unique suffix.')
+var varUniqueKeyVaultName = '${parKeyVaultName}-${varUniqueSuffix}'
+
+@description('SQL Server name with unique suffix.')
+var varUniqueSqlServerName = '${parSqlServerName}-${varUniqueSuffix}'
+
+@description('Virtual Machine name with unique suffix.')
+var varUniqueVmName = '${parVmName}-${varUniqueSuffix}'
+
+@description('Confidential Ledger name with unique suffix.')
+var varUniqueLedgerName = '${parLedgerName}-${varUniqueSuffix}'
+
+@description('Attestation Provider name with unique suffix.')
+var varUniqueAttestationProviderName = '${parAttestationProvidersName}${varUniqueSuffix}'
+
+@description('The database connection string (without authentication details, since we are using service principals for RBAC) that the VM will use to query the SQL server.')
+var varVmUserData = '{ "ContosoHRDatabase":"Data Source = ${varUniqueSqlServerName}${environment().suffixes.sqlServerHostname}; Initial Catalog = ${parSqlDatabaseName}; Column Encryption Setting = Enabled;Attestation Protocol = AAS; Enclave Attestation Url = https://sharedwus.wus.attest.azure.net/attest/SgxEnclave; Authentication=Active Directory Managed Identity;","ConfidentialLedgerName": "${varUniqueLedgerName}" }'
+
+resource resResourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+ location: parDeploymentLocation
+ name: parResourceGroupName
+ tags: parTags
+}
+
+module modManagedIdentity '../../../../common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep' = {
+ name: 'App-Managed-Identity-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parLocation: parDeploymentLocation
+ parName: parManagedIdentityName
+ parTags: parTags
+ }
+}
+
+module modCommonInfra './hrAppInfra.bicep' = {
+ name: 'App-Workload-Common-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parManagedIdentityName: modManagedIdentity.outputs.outName
+ parAttestationProvidersName: varUniqueAttestationProviderName
+ parKeyVaultName: varUniqueKeyVaultName
+ parDeploymentLocation: parDeploymentLocation
+ parVirtualNetworkName: parVirtualNetworkName
+ parNetworkSecurityGroupName: parNetworkSecurityGroupName
+ parTags: parTags
+ parVnetAddressPrefix: parVnetAddressPrefix
+ parSubnetAddressPrefix: parVnetAddressPrefix
+ parHubNetworkResourceId: parHubNetworkResourceId
+ parTimestamp: parTimestamp
+ parKeyVaultOrMhsmAdminObjectId: parDeployingUserObjectId
+ parAttestationContributorObjectId: parDeployingUserObjectId
+ parKeyExpirationDate: parKeyExpirationDate
+ }
+}
+
+module modSqlServer '../../sqlServer/template.sqlserver.bicep' = {
+ name: 'App-Workload-SqlServer-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parTags: parTags
+ parKeyVaultName: varUniqueKeyVaultName
+ parSqlAdministratorLogin: parSqlAdministratorLogin
+ parSqlAdministratorLoginPassword: parSqlAdministratorLoginPassword
+ parDeploymentLocation: parDeploymentLocation
+ parVirtualNetworkName: parVirtualNetworkName
+ parSqlServerName: varUniqueSqlServerName
+ parDatabaseName: parSqlDatabaseName
+ parSqlKeyName: modCommonInfra.outputs.outSqlkeyName
+ parSqlKeyUri: modCommonInfra.outputs.outSqlkeyUrl
+ parAttestationProvidersName: varUniqueAttestationProviderName
+ parSqlServerDnsZoneId: modCommonInfra.outputs.outSqlServerDnsZoneId
+ parAzureAdAdminObjectId: parDeployingUserObjectId
+ parAzureAdAdminUpn: parDeployingUserUpn
+ parManagedIdentityName: parManagedIdentityName
+ }
+ dependsOn: [modCommonInfra]
+}
+
+module modIntelVM '../../virtualMachines/template.confidentialvm.bicep' = {
+ name: 'App-Workload-CVM-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parTags: parTags
+ parKeyVaultName: varUniqueKeyVaultName
+ parDiskEncryptionSetId: modCommonInfra.outputs.outDiskEncryptionSetId
+ parVmkeyId: modCommonInfra.outputs.outVmkeyId
+ parVmName: varUniqueVmName
+ parOsImageName: parOsImageName
+ parAdminUsername: parAdminUsername
+ parAdminPasswordOrKey: parAdminPasswordOrKey
+ parAuthenticationType: parAuthenticationType
+ parDeploymentLocation: parDeploymentLocation
+ parAttestationUri: modCommonInfra.outputs.outAttestationProviderAttestUri
+ parAttestationProvidersName: varUniqueAttestationProviderName
+ parVirtualNetworkName: parVirtualNetworkName
+ parVmCustomData: parVmCustomData
+ parVmUserData: varVmUserData
+ }
+ dependsOn: [modCommonInfra]
+}
+
+module modConfidentialLedger '../../azureConfidentialLedger/template.acl.bicep' = {
+ scope: resResourceGroup
+ name: 'App-Workload-ConfidentialLedger-${parTimestamp}'
+ params: {
+ parDeploymentLocation: parDeploymentLocation
+ parAdministratorUserObjectIds: [parDeployingUserObjectId, modIntelVM.outputs.outVmServicePrincipalObjectId]
+ parLedgerName: varUniqueLedgerName
+ parTags: parTags
+ }
+}
+
+output outResourceGroupName string = resResourceGroup.name
+output outAttestationProviderName string = modCommonInfra.outputs.outAttestationProviderName
+output outSqlServerName string = modSqlServer.outputs.outSqlServerName
+output outSqlDatabaseName string = modSqlServer.outputs.outSqlDatabaseName
+output outVmName string = modIntelVM.outputs.outVmName
+output outCmkUrl string = modCommonInfra.outputs.outSqlCmkUrl
+output outSqlAdministratorLogin string = parSqlAdministratorLogin
diff --git a/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.ps1 b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.ps1
new file mode 100644
index 0000000..2533953
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/scripts/hrAppWorkload.ps1
@@ -0,0 +1,200 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script serves as the overarching script to deploy the HR Sample App Workload either in its entirety or in a piecemeal manner the below individual modules.
+
+.DESCRIPTION
+- Executes the individual modules - hr app sample workload
+
+Prerequisites:
+
+Connect-AzAccount -Subscription %SUBSCRIPTION_ID%
+Add-SqlAzureAuthenticationContext -Interactive
+#>
+
+using namespace System.Collections
+param (
+ [Parameter(Mandatory = $false, Position = 0)]
+ [string] $parRootDeploymentLocation = "eastus",
+ [Parameter(Mandatory = $false, Position = 1)]
+ [bool] $parInitializeDatabase = $true
+)
+
+#reference to common scripts
+. "..\..\..\..\common\common.ps1"
+
+# Retry logic parameters (in case of transient errors)
+$varMaxTransientErrorRetryAttempts = 3
+$varRetryWaitTime = 60
+
+#bicep files
+# TODO: Figure out this unusual pathing. Is it a requirement to call this script from a much higher working directory?
+$varHrAppWorkload = '.\hrAppWorkload.bicep'
+$varHRAppParametersFile = '.\parameters\hrAppWorkload.parameters.json'
+
+<#
+.DESCRIPTION
+ Deploys HR app Azure resources.
+#>
+function New-AppResourceDeployment {
+ param(
+ [Parameter(Mandatory = $True, Position = 0)]
+ [string] $parDeployingUserObjectId,
+ [Parameter(Mandatory = $True, Position = 1)]
+ [string] $parDeployingUserUpn,
+ [Parameter(Mandatory = $False, Position = 2)]
+ [bool] $parIsValidation = $False
+ )
+
+ $varDonotRetryErrorCodes = Get-DonotRetryErrorCodes '../../../../common/const/doNotRetryErrorCodes.json'
+ $varLoopCounter = 0
+ while ($varLoopCounter -lt $varMaxTransientErrorRetryAttempts) {
+ try {
+ Write-Information ">>> Starting deployment of HR App Azure resources." -InformationAction Continue
+ $varTimestamp = Get-Date -Format FileDateTimeUniversal
+ $varAppDeployment = $null
+ $varDeploymentName = "HrApp-$varTimestamp"
+ if ($parIsValidation) {
+ $varAppDeployment = Test-AzDeployment `
+ -Name $varDeploymentName `
+ -Location $parRootDeploymentLocation `
+ -TemplateFile $varHrAppWorkload `
+ -TemplateParameterFile $varHRAppParametersFile `
+ -parDeployingUserObjectId $parDeployingUserObjectId `
+ -parDeployingUserUpn $parDeployingUserUpn `
+
+ if ($varAppDeployment.Count -gt 0) {
+ Write-Error $varAppDeployment[0].Message -ErrorAction Stop
+ }
+
+ Write-Information ">>> Successfully validated input parameter file." -InformationAction Continue
+ }
+ else {
+ $varAppDeployment = New-AzDeployment `
+ -Name $varDeploymentName `
+ -Location $parRootDeploymentLocation `
+ -TemplateFile $varHrAppWorkload `
+ -TemplateParameterFile $varHRAppParametersFile `
+ -parDeployingUserObjectId $parDeployingUserObjectId `
+ -parDeployingUserUpn $parDeployingUserUpn `
+ -WarningAction Ignore `
+
+ if (!$varAppDeployment -or $varAppDeployment.ProvisioningState -eq "Failed") {
+ Write-Error "Error while executing HR App Azure resources deployment." -ErrorAction Stop
+ }
+ else {
+ Write-Information ">>> Successfully deployed HR App Azure resources." -InformationAction Continue
+ }
+ }
+
+ return $varAppDeployment
+ }
+ catch {
+ $varLoopCounter++
+ $varException = $_.Exception
+ $varErrorDetails = $_.ErrorDetails
+ $varTrace = $_.ScriptStackTrace
+ if ($null -ne $varException) {
+ $errorCode = $varAppDeployment[0].Code
+ }
+
+ Write-Error "$varException \n $varErrorDetails \n $varTrace" -ErrorAction Continue
+
+ if ($varDonotRetryErrorCodes -notcontains $errorCode -and $varLoopCounter -lt $varMaxTransientErrorRetryAttempts) {
+ Write-Information ">>> A deployment error occured, see above. The error may be transient. Retrying deployment after waiting for $varRetryWaitTime seconds." -InformationAction Continue
+ Start-Sleep -Seconds $varRetryWaitTime
+ }
+ else {
+ if ($varLoopCounter -eq $varMaxTransientErrorRetryAttempts) {
+ Write-Information ">>> Maximum number of retry attempts reached. Cancelling deployment." -InformationAction Continue
+ }
+ Write-Error ">>> Error occurred in HR App Workload deployment. Please try after addressing the error : $varException \n $varErrorDetails \n $varTrace" -ErrorAction Stop
+ }
+ }
+ }
+}
+
+function Initialize-AppDatabase() {
+ param(
+ [Parameter(Mandatory = $True, Position = 0)]
+ [PSCustomObject] $parResourceDeploymentOutputs
+ )
+
+ $varDeploymentParameters = Get-Content -Path $varHRAppParametersFile | ConvertFrom-Json
+ $parSqlAdministratorPassword = $varDeploymentParameters.parameters.parSqlAdministratorLoginPassword.value
+
+ # Get remaining outputs from deployment result
+ $varResourceGroupName = $parResourceDeploymentOutputs.outResourceGroupName.value
+ $varAttestationProviderName = $parResourceDeploymentOutputs.outAttestationProviderName.value
+ $varSqlServerName = $parResourceDeploymentOutputs.outSqlServerName.value
+ $varSqlDatabaseName = $parResourceDeploymentOutputs.outSqlDatabaseName.value
+ $varVmName = $parResourceDeploymentOutputs.outVmName.value
+ $varCmkUrl = $parResourceDeploymentOutputs.outCmkUrl.value
+ $parSqlAdministratorLogin = $parResourceDeploymentOutputs.outSqlAdministratorLogin.value
+
+ Write-Information ">>> Initializing app database." -InformationAction Continue
+ # Initiate database population (which due to cmdlet version requirements needs to run under PS5)
+ $varLoopCounter = 0
+ while ($varLoopCounter -lt $varMaxTransientErrorRetryAttempts) {
+ $varLoopCounter++
+ try {
+ .\initializeDatabase.ps1 `
+ -parResourceGroupName $varResourceGroupName `
+ -parAttestationProviderName $varAttestationProviderName `
+ -parSqlServerName $varSqlServerName `
+ -parSqlDatabaseName $varSqlDatabaseName `
+ -parSqlAdminUser $parSqlAdministratorLogin `
+ -parSqlAdminPassword $parSqlAdministratorPassword `
+ -parVmServicePrincipalName $varVmName `
+ -parColumnMasterKeyUrl $varCmkUrl
+
+ return
+ }
+ catch {
+ $varLoopCounter++
+ $varException = $_.Exception
+ $varErrorDetails = $_.ErrorDetails
+ $varTrace = $_.ScriptStackTrace
+ Write-Error "$varException \n $varErrorDetails \n $varTrace" -ErrorAction Continue
+
+ if ($varLoopCounter -eq $varMaxTransientErrorRetryAttempts) {
+ Write-Information ">>> Maximum number of retry attempts reached. Cancelling deployment." -InformationAction Continue
+ Write-Error ">>> Error occurred in initializing database deployment. Please try after addressing the error : $varException \n $varErrorDetails \n $varTrace" -ErrorAction Stop
+ }
+ }
+ }
+}
+
+# Begin execution
+# Preliminaries
+$varAzContext = Get-AzContext
+$varAzContextUserObjectId = $varAzContext.Account.ExtendedProperties.HomeAccountId.Split('.')[0]
+$varAzContextAccountId = $varAzContext.Account.Id
+
+#Check the deployment location
+$varAllowedLocations = @("eastus", "westus", "northeurope", "westeurope")
+$varAppWorkloadParameters = Get-Content -Path $varHRAppParametersFile | ConvertFrom-Json
+$varDeploymentLocation = $varAppWorkloadParameters.parameters.psobject.properties | Where-Object { $_.Name -eq "parDeploymentLocation" }
+if ($null -eq $varDeploymentLocation -or $varDeploymentLocation.Value.value -notin $varAllowedLocations) {
+ Write-Error ">>> parDeploymentLocation in the parameter file can only be eastus or westus or northeurope or westeurope. These are the only locations that support both Azure Confidential Ledger and AMD CVM DCasv5-series." -ErrorAction Stop
+}
+
+# Validate the app resource deployment script with the values from parameter file.
+New-AppResourceDeployment `
+ -parDeployingUserObjectId $varAzContextUserObjectId `
+ -parDeployingUserUpn $varAzContextAccountId `
+ -parIsValidation $True
+
+Register-Compute
+
+# Create the app resource deployment in Azure and parse the returned object to retrieve outputs
+$varAppDeployment = New-AppResourceDeployment `
+ -parDeployingUserObjectId $varAzContextUserObjectId `
+ -parDeployingUserUpn $varAzContextAccountId
+
+# Load content into the Azure SQL Database (turning on Always Encrypt)
+if ($parInitializeDatabase) {
+ Initialize-AppDatabase `
+ -parResourceDeploymentOutputs $varAppDeployment.Outputs
+}
diff --git a/sovereignApplications/confidential/hrAppWorkload/scripts/initializeDatabase.ps1 b/sovereignApplications/confidential/hrAppWorkload/scripts/initializeDatabase.ps1
new file mode 100644
index 0000000..38004d2
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/scripts/initializeDatabase.ps1
@@ -0,0 +1,125 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script initializes database which is called by hrAppWorkload.ps1.
+.DESCRIPTION
+- Executes the individual modules - hr app sample workload
+
+Prerequisites:
+
+Connect-AzAccount -Subscription %SUBSCRIPTION_ID%
+Add-SqlAzureAuthenticationContext -Interactive
+#>
+
+Param(
+ [Parameter(Mandatory=$True,Position=0)]
+ [String] $parResourceGroupName,
+ [Parameter(Mandatory=$True,Position=1)]
+ [String] $parAttestationProviderName,
+ [Parameter(Mandatory=$True,Position=2)]
+ [String] $parSqlServerName,
+ [Parameter(Mandatory=$True,Position=3)]
+ [String] $parSqlDatabaseName,
+ [Parameter(Mandatory=$True,Position=4)]
+ [String] $parSqlAdminUser,
+ [Parameter(Mandatory=$True,Position=5)]
+ [String] $parSqlAdminPassword,
+ [Parameter(Mandatory=$True,Position=6)]
+ [String] $parVmServicePrincipalName,
+ [Parameter(Mandatory=$True,Position=7)]
+ [String] $parColumnMasterKeyUrl
+)
+
+$erroractionPreference = "Stop"
+
+Write-Information ">>> Waiting 3 minutes for Azure DNS propagation to take place." -InformationAction Continue
+Start-Sleep -Seconds 180
+
+try {
+ # Load required PS modules
+ Import-Module Az.Attestation
+ Import-Module SqlServer
+
+ # Configure attestation provider policy
+ Write-Information ">>> Setting Attestation Provider policy." -InformationAction Continue
+ $varPolicyFile = "../../../../common/attestationpolicy.txt"
+ $varTeeType = "SgxEnclave"
+ $varPolicyFormat = "Text"
+ $varPolicy=Get-Content -path $varPolicyFile -Raw
+ Set-AzAttestationPolicy -Name $parAttestationProviderName `
+ -ResourceGroupName $parResourceGroupName `
+ -Tee $varTeeType `
+ -Policy $varPolicy `
+ -PolicyFormat $varPolicyFormat
+
+ # SQL server details
+ $varSqlServerFQDN="${parSqlServerName}.database.windows.net"
+ $varDbConnectionString="Server=tcp:$varSqlServerFQDN,1433;Initial Catalog=$parSqlDatabaseName;Persist Security Info=False;Column Encryption Setting = Enabled;User ID=$parSqlAdminUser;Password=$parSqlAdminPassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
+
+ # Assign database read access to the VM service principal identity
+ # New AAD users can only be added by the AAD admin, so we get a token for that user first
+ Write-Information ">>> Adding database user for CVM service principal." -InformationAction Continue
+ $varAccessToken=(Get-AzAccessToken -ResourceUrl https://database.windows.net/).Token
+ $varParams = @{
+ 'Database' = $parSqlDatabaseName
+ 'ServerInstance' = $varSqlServerFQDN
+ 'AccessToken' = $varAccessToken
+ 'Query' = "CREATE USER [$parVmServicePrincipalName] FROM EXTERNAL PROVIDER; ALTER ROLE db_owner ADD MEMBER [$parVmServicePrincipalName];"
+ }
+ Invoke-Sqlcmd @varParams
+
+ # Create schema
+ Write-Information ">>> Creating database schema." -InformationAction Continue
+ $varParams = @{
+ 'ConnectionString' = $varDbConnectionString
+ 'Query' = "CREATE SCHEMA [HR];"
+ }
+ Invoke-Sqlcmd @varParams
+
+ # Create table
+ Write-Information ">>> Creating database table." -InformationAction Continue
+ $varParams = @{
+ 'ConnectionString' = $varDbConnectionString
+ 'InputFile' = "../../contosoHR/data/schema.sql"
+ }
+ Invoke-Sqlcmd @varParams
+
+ # Upload data
+ Write-Information ">>> Uploading database contents." -InformationAction Continue
+ $varParams = @{
+ 'ConnectionString' = $varDbConnectionString
+ 'InputFile' = "../../contosoHR/data/data.sql"
+ }
+ Invoke-Sqlcmd @varParams
+
+ # Generate CMK settings using key from key vault
+ $varCmkSettings = New-SqlAzureKeyVaultColumnMasterKeySettings -KeyURL $parColumnMasterKeyUrl -AllowEnclaveComputations
+
+ # Provision CMK and CEK to SQL Server
+ Write-Information ">>> Provisioning Always Encrypted keys." -InformationAction Continue
+ $Database = Get-SqlDatabase -ConnectionString $varDbConnectionString
+ New-SqlColumnMasterKey -Name "CMK1" -InputObject $Database -ColumnMasterKeySettings $varCmkSettings
+ New-SqlColumnEncryptionKey -Name "CEK1" -InputObject $Database -ColumnMasterKey "CMK1"
+
+ # Encrypt the selected columns
+ Write-Information ">>> Encrypting SSN and Salary columns." -InformationAction Continue
+ $CES = @()
+ $CES += New-SqlColumnEncryptionSettings -ColumnName "HR.Employees.SSN" -EncryptionType "Randomized" -EncryptionKey "CEK1"
+ $CES += New-SqlColumnEncryptionSettings -ColumnName "HR.Employees.Salary" -EncryptionType "Randomized" -EncryptionKey "CEK1"
+ Set-SqlColumnEncryption -InputObject $Database -ColumnEncryptionSettings $CES -UseOnlineApproach -LogFileDirectory .
+
+ Write-Information ">>> Database initialization complete." -InformationAction Continue
+}
+catch {
+ $errorMessage = ""
+ $exception = $_.Exception.ToString()
+ if ($null -ne $exception) {
+ $errorMessage = $exception
+ }
+ else {
+ $errorMessage = $error[0].ToString()
+ }
+
+ throw $errorMessage
+}
diff --git a/sovereignApplications/confidential/hrAppWorkload/scripts/parameters/hrAppWorkload.parameters.json b/sovereignApplications/confidential/hrAppWorkload/scripts/parameters/hrAppWorkload.parameters.json
new file mode 100644
index 0000000..8ffb42f
--- /dev/null
+++ b/sovereignApplications/confidential/hrAppWorkload/scripts/parameters/hrAppWorkload.parameters.json
@@ -0,0 +1,49 @@
+{
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parDeploymentLocation": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": "eastus",
+ "description": "Location of the deployment."
+ },
+ "parHubNetworkResourceId": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": null,
+ "description": "Resource ID of the SLZ's hub vnet from the %slzprefix%-connectivity subscription."
+ },
+ "parAdminPasswordOrKey": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": null,
+ "description": "The password or public key (see option parAuthenticationType) for the Admin user created on Confidential Virtual Machine."
+ },
+ "parSqlAdministratorLoginPassword": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": null,
+ "description": "The password for the administrator user created on the SQL Server."
+ },
+ "parDeploymentPrefix": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "minLength": 2,
+ "maxLength": 5,
+ "value": null,
+ "description": "Prefix added to some of the resources deployed."
+ },
+ "parVnetAddressPrefix": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": null,
+ "description": "IP address space of the VNET created for this workload, in CIDR notation. Must be unique across all vnets peered against the SLZ hub vnet as referenced above."
+ },
+ "parAuthenticationType": {
+ "type": "string",
+ "usedBy": "hrapp",
+ "value": "password",
+ "description": "The authentication type of the VM."
+ }
+ }
+}
diff --git a/sovereignApplications/confidential/sqlServer/README.md b/sovereignApplications/confidential/sqlServer/README.md
new file mode 100644
index 0000000..cda0a60
--- /dev/null
+++ b/sovereignApplications/confidential/sqlServer/README.md
@@ -0,0 +1,3 @@
+# Contoso HR Sample Application SQL Database
+
+This sample application is part of the [hrAppWorkload](../hrAppWorkload) confidential sample application and deploys the Azure SQL Database resources required by the Contoso HR sample application.
\ No newline at end of file
diff --git a/sovereignApplications/confidential/sqlServer/template.sqlserver.bicep b/sovereignApplications/confidential/sqlServer/template.sqlserver.bicep
new file mode 100644
index 0000000..e97068a
--- /dev/null
+++ b/sovereignApplications/confidential/sqlServer/template.sqlserver.bicep
@@ -0,0 +1,342 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the Azure SQL Server and employee database.
+DESCRIPTION: This module will create a deployment which will create the Azure SQL Server and employee database.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Location for all resources.')
+param parDeploymentLocation string
+
+@description('List of tags to be applied to resources.')
+param parTags object
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string
+
+@description('Key vault name')
+param parKeyVaultName string
+
+@description('Name of the virtual network')
+param parVirtualNetworkName string
+
+@description('The Private DNS zone that the Private Endpoint for this SQL Server will link to.')
+param parSqlServerDnsZoneId string
+
+@description('The administrator username of the SQL logical server')
+param parSqlAdministratorLogin string
+
+@description('The administrator password of the SQL logical server.')
+@secure()
+param parSqlAdministratorLoginPassword string
+
+@description('User Principal Name of the Azure AD user that will be set as admin for this SQL Server.')
+param parAzureAdAdminUpn string
+
+@description('Object ID of the Azure AD user that will be set as admin for this SQL Server.')
+param parAzureAdAdminObjectId string
+
+@description('Sql Server Name ')
+param parSqlServerName string
+
+@description('Database Name')
+param parDatabaseName string
+
+@description('Sql Key Name')
+param parSqlKeyName string
+
+@description('Sql Key Uri')
+param parSqlKeyUri string
+
+@description('Attestation providers name')
+param parAttestationProvidersName string
+
+var varSubnetName = 'subnet'
+var varSubnetRef = resourceId('Microsoft.Network/virtualNetworks/subnets', parVirtualNetworkName, varSubnetName)
+var varSqlServerKeyName = '${parKeyVaultName}_${parSqlKeyName}_${substring(parSqlKeyUri, lastIndexOf(parSqlKeyUri, '/')+1)}'
+var varSqlServerPrivateEndpointName = 'endpoint-${parSqlServerName}'
+
+// Existing resources stood up by common module or provided by Azure
+resource resKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
+ name: parKeyVaultName
+}
+resource resAttestationProvider 'Microsoft.Attestation/attestationProviders@2021-06-01-preview' existing = {
+ name: parAttestationProvidersName
+}
+
+// The built-in Key Vault Administrator role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator
+resource resKeyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
+}
+
+// The built-in Attestation Reader role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#attestation-reader
+resource resAttestationProviderAttestationReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: 'fd1bd22b-8476-40bc-a0bc-69b95687b9f3'
+}
+
+resource resManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
+ name: parManagedIdentityName
+}
+
+resource resSqlServer 'Microsoft.Sql/servers@2021-11-01' = {
+ name: parSqlServerName
+ location: parDeploymentLocation
+ tags: parTags
+ identity: {
+ type: 'SystemAssigned, UserAssigned'
+ userAssignedIdentities: {
+ '${resManagedIdentity.id}': {
+ }
+ }
+ }
+ properties: {
+ keyId: parSqlKeyUri
+ administratorLogin: parSqlAdministratorLogin
+ administratorLoginPassword: parSqlAdministratorLoginPassword
+ administrators: {
+ administratorType: 'ActiveDirectory'
+ azureADOnlyAuthentication: false
+ login: parAzureAdAdminUpn
+ sid: parAzureAdAdminObjectId
+ principalType: 'User'
+ tenantId: tenant().tenantId
+ }
+ version: '12.0'
+ minimalTlsVersion: '1.2'
+ publicNetworkAccess: 'Disabled'
+ primaryUserAssignedIdentityId: resManagedIdentity.id
+ }
+}
+
+// Assign the SQL Server service principal full rights on key vault. Could probably be tightened.
+resource resSqlServerKeyVaultAdminRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parSqlServerName, resKeyVaultAdministratorRoleDefinition.id)
+ scope: resKeyVault
+ properties: {
+ principalId: resSqlServer.identity.principalId
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+// Assign the SQL Server service principal reader rights on attestation provider.
+resource resSqlServerAttestationProviderAttestationReaderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parSqlServerName, resAttestationProviderAttestationReaderRoleDefinition.id)
+ scope: resAttestationProvider
+ properties: {
+ principalId: resSqlServer.identity.principalId
+ roleDefinitionId: resAttestationProviderAttestationReaderRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+resource resSqlServerNameDatabase 'Microsoft.Sql/servers/databases@2021-11-01' = {
+ parent: resSqlServer
+ name: parDatabaseName
+ location: parDeploymentLocation
+ tags: parTags
+ sku: {
+ name: 'GP_DC_2'
+ tier: 'GeneralPurpose'
+ }
+ properties: {
+ collation: 'SQL_Latin1_General_CP1_CI_AS'
+ maxSizeBytes: 34359738368
+ sampleName: 'AdventureWorksLT'
+ zoneRedundant: false
+ licenseType: 'LicenseIncluded'
+ readScale: 'Disabled'
+ highAvailabilityReplicaCount: 0
+ isLedgerOn: false
+ }
+}
+
+resource resSqlServerKey 'Microsoft.Sql/servers/keys@2021-11-01' = {
+ parent: resSqlServer
+ name: varSqlServerKeyName
+ properties: {
+ serverKeyType: 'AzureKeyVault'
+ uri: parSqlKeyUri
+ }
+}
+
+resource resDevOpsAuditingSettingsSqlServerNameDefault 'Microsoft.Sql/servers/devOpsAuditingSettings@2021-11-01' = {
+ parent: resSqlServer
+ name: 'default'
+ properties: {
+ isAzureMonitorTargetEnabled: false
+ state: 'Disabled'
+ storageAccountSubscriptionId: '00000000-0000-0000-0000-000000000000'
+ }
+}
+
+resource resSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/advancedThreatProtectionSettings@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ state: 'Disabled'
+ }
+}
+
+resource resAuditingPoliciesSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/auditingPolicies@2014-04-01' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ auditingState: 'Disabled'
+ }
+}
+
+resource resAuditingSettingsSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/auditingSettings@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ retentionDays: 0
+ auditActionsAndGroups: []
+ isStorageSecondaryKeyInUse: false
+ isAzureMonitorTargetEnabled: false
+ isManagedIdentityInUse: false
+ state: 'Disabled'
+ storageAccountSubscriptionId: '00000000-0000-0000-0000-000000000000'
+ }
+}
+
+resource resBackupLongTermRetentionPoliciesSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ weeklyRetention: 'PT0S'
+ monthlyRetention: 'PT0S'
+ yearlyRetention: 'PT0S'
+ weekOfYear: 1
+ }
+}
+
+resource resBackupShortTermRetentionPoliciesSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ retentionDays: 7
+ diffBackupIntervalInHours: 12
+ }
+}
+
+resource resExtendedAuditingSettingsSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/extendedAuditingSettings@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ retentionDays: 0
+ auditActionsAndGroups: []
+ isStorageSecondaryKeyInUse: false
+ isAzureMonitorTargetEnabled: false
+ isManagedIdentityInUse: false
+ state: 'Disabled'
+ storageAccountSubscriptionId: '00000000-0000-0000-0000-000000000000'
+ }
+}
+
+resource resVulnerabilityAssessmentsSqlServerNameDatabaseDefault 'Microsoft.Sql/servers/databases/vulnerabilityAssessments@2022-05-01-preview' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ recurringScans: {
+ isEnabled: false
+ emailSubscriptionAdmins: true
+ emails: []
+ }
+ }
+}
+
+resource resSqlServerNameCurrent 'Microsoft.Sql/servers/encryptionProtector@2022-05-01-preview' = {
+ parent: resSqlServer
+ name: 'current'
+ properties: {
+ autoRotationEnabled: true
+ serverKeyName: varSqlServerKeyName
+ serverKeyType: 'AzureKeyVault'
+ }
+ dependsOn: [
+ resSqlServerKey
+ ]
+}
+
+resource resExtendedAuditingSettingsSqlServerNameDefault 'Microsoft.Sql/servers/extendedAuditingSettings@2021-11-01' = {
+ parent: resSqlServer
+ name: 'default'
+ properties: {
+ retentionDays: 0
+ auditActionsAndGroups: []
+ isStorageSecondaryKeyInUse: false
+ isAzureMonitorTargetEnabled: false
+ isManagedIdentityInUse: false
+ state: 'Disabled'
+ storageAccountSubscriptionId: '00000000-0000-0000-0000-000000000000'
+ }
+}
+
+resource resGeoBackupPoliciesSqlServerNameDatabaseNameDefault 'Microsoft.Sql/servers/databases/geoBackupPolicies@2021-11-01' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ state: 'Enabled'
+ }
+}
+
+resource resSecurityAlertPoliciesSqlServerNameDatabaseNameDefault 'Microsoft.Sql/servers/databases/securityAlertPolicies@2021-11-01' = {
+ parent: resSqlServerNameDatabase
+ name: 'default'
+ properties: {
+ state: 'Disabled'
+ disabledAlerts: [
+ ''
+ ]
+ emailAddresses: [
+ ''
+ ]
+ emailAccountAdmins: false
+ retentionDays: 0
+ }
+}
+
+resource resSqlServerPrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = {
+ name: varSqlServerPrivateEndpointName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ subnet: {
+ id: varSubnetRef
+ }
+ customNetworkInterfaceName: 'nic-${varSqlServerPrivateEndpointName}'
+ privateLinkServiceConnections: [
+ {
+ name: varSqlServerPrivateEndpointName
+ properties: {
+ privateLinkServiceId: resSqlServer.id
+ groupIds: [
+ 'sqlServer'
+ ]
+ }
+ }
+ ]
+ }
+}
+
+// Register Private Endpoint with Private DNS zone to allow DNS resolution on SLZ VNets
+resource resSqlServerPrivateEndpointZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-07-01' = {
+ name: 'default'
+ parent: resSqlServerPrivateEndpoint
+ properties: {
+ privateDnsZoneConfigs: [
+ {
+ name: 'privatelink-database-windows-net'
+ properties: {
+ privateDnsZoneId: parSqlServerDnsZoneId
+ }
+ }
+ ]
+ }
+}
+
+output outSqlServerName string = resSqlServer.name
+output outSqlDatabaseName string = resSqlServerNameDatabase.name
diff --git a/sovereignApplications/confidential/virtualMachines/README.md b/sovereignApplications/confidential/virtualMachines/README.md
new file mode 100644
index 0000000..d13d1cd
--- /dev/null
+++ b/sovereignApplications/confidential/virtualMachines/README.md
@@ -0,0 +1,3 @@
+# Contoso HR Sample Application Virtual Machine
+
+This sample application is part of the [hrAppWorkload](../hrAppWorkload) confidential sample application and deploys the Azure Virtual Machine resources required by the Contoso HR sample application. The Contoso HR sample is running its web components inside of this confidential Virtual Machine.
\ No newline at end of file
diff --git a/sovereignApplications/confidential/virtualMachines/template.confidentialvm.bicep b/sovereignApplications/confidential/virtualMachines/template.confidentialvm.bicep
new file mode 100644
index 0000000..4461012
--- /dev/null
+++ b/sovereignApplications/confidential/virtualMachines/template.confidentialvm.bicep
@@ -0,0 +1,311 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the confidential virtual machine.
+DESCRIPTION: This module will create a deployment which will create the confidential virtual machine and virtualMachines' extensions.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Location for all resources.')
+param parDeploymentLocation string
+
+@description('List of tags to be applied to resources.')
+param parTags object
+
+@description('Key vault name')
+param parKeyVaultName string
+
+@description('Virtual network that the Confidential VM connects to.')
+param parVirtualNetworkName string
+
+@description('The name of the Confidential VM (resource and host name).')
+param parVmName string
+
+@description('Is the AMD Confidential VM.')
+param parIsAMDVM bool = true
+
+@description('The size of the virtual machine')
+@allowed([
+ 'Standard_DC2s_v2'
+ 'Standard_DC2as_v5'
+ 'Standard_DC4as_v5'
+])
+param parVmSize string = 'Standard_DC2as_v5'
+
+@description('OS image for the virtual Machine')
+@allowed([
+ 'Windows Server 2022 Gen 2'
+ 'Windows Server 2019 Gen 2'
+ 'Ubuntu 22.04 LTS Gen 2'
+ 'Ubuntu 20.04 LTS Gen 2'
+])
+param parOsImageName string
+
+@description('OS disk type of the VM.')
+@allowed([
+ 'Premium_LRS'
+ 'Standard_LRS'
+ 'StandardSSD_LRS'
+])
+param parOsDiskType string = 'StandardSSD_LRS'
+
+@description('Username for the virtual machine.')
+param parAdminUsername string
+
+@description('Password for the virtual machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
+@secure()
+param parAdminPasswordOrKey string
+
+@description('Type of authentication to use on the virtual machine. SSH Public key is recommended.')
+@allowed([
+ 'sshPublicKey'
+ 'password'
+])
+param parAuthenticationType string
+
+@description('Boolean value indicating whether the VM supports secured boot, e.g., true.')
+param parSecureBoot bool = true
+
+@description('Boolean value indicating whether the VM is using virtualized version of a hardware Trusted Platform Module,e.g., true.')
+param parVTPM bool = true
+
+@description('Disk Encryption Set Id.')
+param parDiskEncryptionSetId string
+
+@description('Virtual Machine Key Id')
+param parVmkeyId string
+
+@description('Virtual machine Properties/osProfile/customData block. Carries a string that a Linux target machine interprets as an industry-standard cloud-init configuration (see https://learn.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init).')
+param parVmCustomData string
+
+@description('Virtual machine Properties/userData block. Carries a string that the VM can access thorugh the IMDS service, see https://learn.microsoft.com/en-us/azure/virtual-machines/user-data.')
+param parVmUserData string
+
+@description('Attestation Provider URI to attest to.')
+param parAttestationUri string
+
+@description('Attestation providers name')
+param parAttestationProvidersName string
+
+var parDiskName = '${parVmName}-osDisk'
+var varDataDiskName = '${parVmName}-dataDisk'
+var varImageList = {
+ 'Windows Server 2022 Gen 2': {
+ publisher: 'microsoftwindowsserver'
+ offer: 'windowsserver'
+ sku: '2022-datacenter-smalldisk-g2'
+ version: 'latest'
+ }
+ 'Windows Server 2019 Gen 2': {
+ publisher: 'microsoftwindowsserver'
+ offer: 'windowsserver'
+ sku: '2019-datacenter-smalldisk-g2'
+ version: 'latest'
+ }
+ 'Ubuntu 22.04 LTS Gen 2': {
+ publisher: 'Canonical'
+ offer: '0001-com-ubuntu-confidential-vm-jammy'
+ sku: '22_04-lts-cvm'
+ version: 'latest'
+ }
+ 'Ubuntu 20.04 LTS Gen 2': {
+ publisher: 'Canonical'
+ offer: '0001-com-ubuntu-confidential-vm-focal'
+ sku: '20_04-lts-cvm'
+ version: 'latest'
+ }
+}
+var varImageReference = varImageList[parOsImageName]
+var varAscReportingEndpoint = resAttestationProvider.properties.attestUri
+var varDisableAlerts = 'false'
+var varExtensionName = 'GuestAttestation'
+var varExtensionPublisher = varIsWindows ? 'Microsoft.Azure.Security.WindowsAttestation' : 'Microsoft.Azure.Security.LinuxAttestation'
+var varExtensionVersion = '1.0'
+var varMaaTenantName = 'GuestAttestation'
+var varUseAlternateToken = 'false'
+var varIsWindows = contains(parOsImageName, 'Windows')
+var varLinuxConfiguration = {
+ disablePasswordAuthentication: 'true'
+ ssh: {
+ publicKeys: [
+ {
+ keyData: parAdminPasswordOrKey
+ path: '/home/${parAdminUsername}/.ssh/authorized_keys'
+ }
+ ]
+ }
+}
+var varWindowsConfiguration = {
+ enableAutomaticUpdates: 'true'
+ provisionVmAgent: 'true'
+}
+
+var varEncryptionOperation = 'EnableEncryption'
+var varKeyEncryptionAlgorithm = 'RSA-OAEP'
+var varKeyVaultResourceID = resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults/', parKeyVaultName)
+
+// Existing resources stood up by common module or provided by Azure
+resource resKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
+ name: parKeyVaultName
+}
+resource resAttestationProvider 'Microsoft.Attestation/attestationProviders@2021-06-01' existing = {
+ name: parAttestationProvidersName
+}
+
+// The built-in Key Vault Administrator role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator
+resource resKeyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
+}
+
+// The built-in Attestation Reader role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#attestation-reader
+resource resAttestationProviderAttestationReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: 'fd1bd22b-8476-40bc-a0bc-69b95687b9f3'
+}
+
+// Virtual network and subnet from common module
+resource resVnet 'Microsoft.Network/virtualNetworks@2022-07-01' existing = {
+ name: parVirtualNetworkName
+}
+
+// Virtual machine
+resource resVm 'Microsoft.Compute/virtualMachines@2021-07-01' = {
+ name: parVmName
+ location: parDeploymentLocation
+ tags: parTags
+ identity: {
+ type: 'SystemAssigned'
+ }
+ properties: {
+ userData: base64(parVmUserData)
+ hardwareProfile: {
+ vmSize: parVmSize
+ }
+ osProfile: {
+ computerName: length(parVmName) <= 15 ? parVmName : replace(take(parVmName,15),'-','') // If vm name is too long, reduce for compliance with Windows naming.
+ adminUsername: parAdminUsername
+ adminPassword: parAdminPasswordOrKey
+ linuxConfiguration: ((parAuthenticationType == 'password') ? null : varLinuxConfiguration)
+ windowsConfiguration: (varIsWindows ? varWindowsConfiguration : null)
+ customData: parVmCustomData
+ }
+ storageProfile: {
+ osDisk: {
+ name: parDiskName
+ createOption: 'FromImage'
+ managedDisk: {
+ securityProfile: {
+ securityEncryptionType: 'VMGuestStateOnly'
+ }
+ storageAccountType: parOsDiskType
+ diskEncryptionSet: {
+ id: parDiskEncryptionSetId
+ }
+ }
+ }
+ dataDisks: [
+ {
+ name: varDataDiskName
+ diskSizeGB: 128
+ lun: 0
+ createOption: 'Empty'
+ managedDisk: {
+ storageAccountType: 'Premium_LRS'
+ diskEncryptionSet: {
+ id: parDiskEncryptionSetId
+ }
+ }
+ }
+ ]
+ imageReference: varImageReference
+ }
+ networkProfile: {
+ networkInterfaces: [
+ {
+ id: resNIC.id
+ }
+ ]
+ }
+ securityProfile: {
+ uefiSettings: {
+ secureBootEnabled: parSecureBoot
+ vTpmEnabled: parVTPM
+ }
+ securityType: 'ConfidentialVM'
+ }
+ }
+}
+
+// Assign the Confidential VM service principal full rights on key vault. Could probably be tightened.
+resource resSqlServerKeyVaultAdminRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parVmName, resKeyVaultAdministratorRoleDefinition.id)
+ scope: resKeyVault
+ properties: {
+ principalId: resVm.identity.principalId
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+// Assign the Confidential VM service principal reader rights on attestation provider.
+resource resSqlServerAttestationProviderAttestationReaderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parVmName, resAttestationProviderAttestationReaderRoleDefinition.id)
+ scope: resAttestationProvider
+ properties: {
+ principalId: resVm.identity.principalId
+ roleDefinitionId: resAttestationProviderAttestationReaderRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+// VM NIC
+resource resNIC 'Microsoft.Network/networkInterfaces@2022-07-01' = {
+ name: 'nic-${parVmName}'
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ ipConfigurations: [
+ {
+ name: 'ipconfig'
+ properties: {
+ privateIPAllocationMethod: 'Dynamic'
+ subnet: {
+ id: resVnet.properties.subnets[0].id
+ }
+ }
+ }
+ ]
+ }
+}
+
+resource vmNameExtension 'Microsoft.Compute/virtualMachines/extensions@2022-08-01' = if (parVTPM && parSecureBoot && parIsAMDVM == false) {
+ parent: resVm
+ name: varExtensionName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ publisher: varExtensionPublisher
+ type: varExtensionName
+ typeHandlerVersion: varExtensionVersion
+ autoUpgradeMinorVersion: true
+ forceUpdateTag: '1.0'
+ settings: {
+ maaEndpoint: parAttestationUri
+ maaTenantName: varMaaTenantName
+ ascReportingEndpoint: varAscReportingEndpoint
+ useAlternateToken: varUseAlternateToken
+ disableAlerts: varDisableAlerts
+ EncryptionOperation: varEncryptionOperation
+ KeyVaultURL: reference(varKeyVaultResourceID, '2022-07-01').vaultUri
+ KeyVaultResourceId: varKeyVaultResourceID
+ KeyEncryptionKeyURL: reference(parVmkeyId, '2022-07-01', 'Full').properties.keyUriWithVersion
+ KekVaultResourceId: varKeyVaultResourceID
+ KeyEncryptionAlgorithm: varKeyEncryptionAlgorithm
+ VolumeType: 'All'
+ ResizeOSDisk: false
+ }
+ }
+}
+
+output outVmName string = resVm.name
+output outVmServicePrincipalObjectId string = resVm.identity.principalId
diff --git a/sovereignApplications/nonConfidential/adminVM/README.md b/sovereignApplications/nonConfidential/adminVM/README.md
new file mode 100644
index 0000000..8c8722f
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/README.md
@@ -0,0 +1,54 @@
+# SLZ Administrative Workstation VM
+
+This script deploys an administrative workstation VM including the software listed to the *Connectivity* subscription of a Sovereign Landing Zone environment. That VM connects to the SLZ hub virtual network and thus have data plane access to administer resources on peered spoke virtual networks. For instance, this admin VM is the recommended deployment platform for [the HR Sovereign App](../../confidential/hrAppWorkload/README.md).
+
+## Deployed Software
+
+* Git
+* Bicep
+* PowerShell 5 modules:
+ * Az
+ * SqlServer
+* SQL Server Management Studio
+ * Including Azure Data Studio
+
+## Prerequisites
+
+* [PowerShell Az module](https://learn.microsoft.com/powershell/azure/install-az-ps)
+* [Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/install#install-manually)
+
+## Instructions
+
+1. Sign in with the following command, where `` is the GUID of the SLZ's *Platform - Connectivity* subscription that the admin VM is added to.
+
+ ``` powershell
+ Login-AzAccount
+
+ Connect-AzAccount -Subscription
+ ```
+
+2. Change directory to `.\sovereignApplications\nonConfidential\adminVM\scripts`
+3. Run ` Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process`
+4. Configure the parameters in `adminvm.parameters.json` as per the [following table](#deployment-parameters) and save the file.
+5. Execute `.\deploy.ps1` from AdminVM folder.
+6. When prompted, supply the password to be set on the VM, and press enter.
+7. Wait until the script returns, which may take 15-20 minutes. You can track deployment through the *Connectivity* subscription's Deployments page in the Azure portal.
+8. The process prompts you to connect to the Admin VM and provide credential info. Follow the illustrations to sign in into the Admin VM: [Admin VM RDP Step1](media/AdminVMRDP-1.jpg), [Admin VM RDP Step2](media/AdminVMRDP-2.jpg), [Admin VM RDP Step3](media/AdminVMRDP-3.jpg)
+9. Continue with sovereign app [deployment](../../confidential/hrAppWorkload/docs/04-deployment.md)
+
+> [!NOTE]
+> You can connect later to admin VM via Bastion. You can find the VM in the Azure portal, click the `Connect/Go to Bastion`, then enter the login credentials for the administration workstation VM and select Connect. See illustrations [Connect to VM 1](../../confidential/hrAppWorkload/media/connect-to-VM1.png), [Connect to VM 2](../../confidential/hrAppWorkload/media/connect-to-VM2.png).
+
+### Deployment Parameters
+
+The parameters listed must be entered in order to deploy this script. Other parameters are available for further customization, see [adminvm.bicep](./scripts/adminvm.bicep).
+
+> [!NOTE]
+> The additional parameter `parSubnetAddressSpace` which defines the subnet IP space used by the admin VM subnet, which needs to be within the hub network ip space but must be disjoint with any other existing subnets (its default value is `10.20.100.0/24`).
+
+ | Parameter |Description | Examples |
+ |---------------------|---------------|------------------------|
+ | `parDeploymentLocation` | Location of the deployed resources. Needs to be consistent with policy set on SLZ environment. | `eastus`, `westus`, `northeurope`, `westeurope`. |
+ | `parHubNetworkRg` | Name of the resource group in which the SLZ hub virtual network is deployed. | `rg--hub-network-eastus` |
+ | `parHubNetwork` | Name of the SLZ hub virtual network. | `hub--eastus` |
+ | `parAdminUsername` | The name of the admin user account created on the VM. | `slzadmin` |
diff --git a/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-1.jpg b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-1.jpg
new file mode 100644
index 0000000..ba9c513
Binary files /dev/null and b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-1.jpg differ
diff --git a/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-2.jpg b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-2.jpg
new file mode 100644
index 0000000..ea94fa8
Binary files /dev/null and b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-2.jpg differ
diff --git a/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-3.jpg b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-3.jpg
new file mode 100644
index 0000000..679c9c5
Binary files /dev/null and b/sovereignApplications/nonConfidential/adminVM/media/AdminVMRDP-3.jpg differ
diff --git a/sovereignApplications/nonConfidential/adminVM/modules/subnet.bicep b/sovereignApplications/nonConfidential/adminVM/modules/subnet.bicep
new file mode 100644
index 0000000..52d317f
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/modules/subnet.bicep
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the subnet of virtual network.
+DESCRIPTION: This module will create a deployment which will create the subnet of virtual network.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'resourceGroup'
+
+param parHubNetwork string
+param parSubnetName string
+param parSubnetAddressSpace string
+
+resource resVnet 'Microsoft.Network/virtualNetworks@2022-09-01' existing = {
+ name: parHubNetwork
+}
+
+resource resSubnet 'Microsoft.Network/virtualNetworks/subnets@2022-09-01' = {
+ name: parSubnetName
+ parent: resVnet
+ properties:{
+ addressPrefix: parSubnetAddressSpace
+ }
+}
+
+output outSubnetId string = resSubnet.id
diff --git a/sovereignApplications/nonConfidential/adminVM/modules/win11vm.bicep b/sovereignApplications/nonConfidential/adminVM/modules/win11vm.bicep
new file mode 100644
index 0000000..f213645
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/modules/win11vm.bicep
@@ -0,0 +1,128 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the admin virtual machine.
+DESCRIPTION: This module will create a deployment which will create the admin virtual machine to execute the scripts.
+AUTHOR/S: Cloud for Sovereignty
+*/
+
+// Location + connectivity
+param parDeploymentLocation string
+param parSubnetId string
+
+// VM user
+@maxLength(20)
+param parAdminUsername string = 'slzadmin'
+@secure()
+param parAdminPassword string
+
+// Default VM hw parameters
+@maxLength(15)
+param parVmName string = 'admin-vm'
+param parVmSize string = 'Standard_D2s_v3'
+param parDiskType string = 'Standard_LRS'
+
+// OS choice
+var varOsImagePublisher = 'MicrosoftWindowsDesktop'
+var varOsImageOffer = 'windows-11'
+var varOsImageSku = 'win11-22h2-pro'
+
+resource resNic 'Microsoft.Network/networkInterfaces@2022-09-01' = {
+ name: '${parVmName}-nic'
+ location: parDeploymentLocation
+ properties: {
+ enableAcceleratedNetworking: true
+ ipConfigurations: [
+ {
+ name: 'ipconfig'
+ properties: {
+ subnet: {
+ id: parSubnetId
+ }
+ privateIPAddressVersion: 'IPv4'
+ privateIPAllocationMethod: 'Dynamic'
+ }
+ }
+ ]
+ }
+}
+
+resource resVm 'Microsoft.Compute/virtualMachines@2022-11-01' = {
+ name: parVmName
+ location: parDeploymentLocation
+ properties: {
+ osProfile: {
+ computerName: parVmName
+ adminUsername: parAdminUsername
+ adminPassword: parAdminPassword
+ allowExtensionOperations: true
+ windowsConfiguration: {
+ patchSettings: {
+ assessmentMode: 'ImageDefault'
+ patchMode: 'AutomaticByOS'
+ enableHotpatching: false
+ }
+ enableVMAgentPlatformUpdates: true
+ }
+ }
+ hardwareProfile: {
+ vmSize: parVmSize
+ }
+ storageProfile: {
+ imageReference: {
+ publisher: varOsImagePublisher
+ offer: varOsImageOffer
+ sku: varOsImageSku
+ version: 'latest'
+ }
+ osDisk: {
+ createOption: 'FromImage'
+ managedDisk: {
+ storageAccountType: parDiskType
+ }
+ deleteOption: 'Delete'
+ diskSizeGB: 127
+ }
+ dataDisks: []
+ }
+ securityProfile: {
+ securityType: 'TrustedLaunch'
+ uefiSettings: {
+ secureBootEnabled: true
+ vTpmEnabled: true
+ }
+ }
+ networkProfile: {
+ networkInterfaces: [
+ {
+ id: resNic.id
+ properties: {
+ deleteOption: 'Delete'
+ primary: true
+ }
+ }
+ ]
+ }
+ }
+}
+
+resource resVmCustomScriptExtension 'Microsoft.Compute/virtualMachines/extensions@2022-11-01' = {
+ name: '${parVmName}-SoftwareInstaller'
+ location: parDeploymentLocation
+ parent: resVm
+ properties: {
+ publisher: 'Microsoft.Compute'
+ type: 'CustomScriptExtension'
+ typeHandlerVersion: '1.10'
+ autoUpgradeMinorVersion: true
+ settings: {
+ fileUris: [
+ 'https://hrwebapp.blob.${environment().suffixes.storage}/contosohr/adminVmSoftwareInstaller.ps1'
+ ]
+ commandToExecute: 'powershell -ExecutionPolicy Unrestricted -File adminVmSoftwareInstaller.ps1'
+ }
+ }
+}
+
+output outVmName string = resVm.name
+output outVmResourceId string = resVm.id
diff --git a/sovereignApplications/nonConfidential/adminVM/scripts/adminVmSoftwareInstaller.ps1 b/sovereignApplications/nonConfidential/adminVM/scripts/adminVmSoftwareInstaller.ps1
new file mode 100644
index 0000000..939ab07
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/scripts/adminVmSoftwareInstaller.ps1
@@ -0,0 +1,43 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script installs the required softwares in the admin virtual machine.
+
+.DESCRIPTION
+- Executes the individual modules to install required softwares in the admin virtual machine.
+
+#>
+
+# Install Powershell modules
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
+Install-PackageProvider -Name NuGet -Force -Scope AllUsers
+Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+Install-Module -Name Az -Repository PSGallery -Force -AllowClobber -Scope AllUsers
+Install-Module -Name SQLServer -Repository PSGallery -Force -Scope AllUsers
+
+# Turn off (SLOW) Invoke-WebRequest progress bar
+$ProgressPreference = 'SilentlyContinue'
+
+# Download Bicep binary
+$bicepInstallDir = $Env:Programfiles + "\Bicep\"
+New-Item -ItemType Directory -Path $bicepInstallDir -Force
+Invoke-WebRequest -UseBasicParsing "https://github.com/Azure/bicep/releases/latest/download/bicep-win-x64.exe" -OutFile "$bicepInstallDir\bicep.exe";
+
+# Add Bicep to PATH
+$path = [Environment]::GetEnvironmentVariable('PATH', 'Machine') -split ';' |
+ Where-Object { $_ -ne $bicepInstallDir }
+$path += $bicepInstallDir
+$newPath = $path -join ';'
+$regkey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
+Set-ItemProperty -Path $regkey -Name 'Path' -Value $newPath -Type ExpandString
+
+# Download Git installer to temp folder and execute quietly
+$GitInstallerPath = $env:TEMP + "\Git-2.40.0-64-bit.exe"
+Invoke-WebRequest -UseBasicParsing "https://github.com/git-for-windows/git/releases/download/v2.40.0.windows.1/Git-2.40.0-64-bit.exe" -OutFile $GitInstallerPath
+Start-Process -FilePath $GitInstallerPath -ArgumentList "/verysilent /norestart" -Wait
+
+# Download SSMS installer to temp folder and execute quietly
+$SsmsInstallerPath = $env:TEMP + "\SSMS-Setup-ENU.exe"
+Invoke-WebRequest -UseBasicParsing "https://aka.ms/ssmsfullsetup" -OutFile $SsmsInstallerPath
+Start-Process -FilePath $SsmsInstallerPath -ArgumentList "/install /quiet /norestart" -Wait
diff --git a/sovereignApplications/nonConfidential/adminVM/scripts/adminvm.bicep b/sovereignApplications/nonConfidential/adminVM/scripts/adminvm.bicep
new file mode 100644
index 0000000..288f969
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/scripts/adminvm.bicep
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the admin virtual machine bicep script.
+DESCRIPTION: This module will create a deployment which will create the admin virtual machine to deploy HR App workload.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'subscription'
+
+// Core deployment/location parameters
+param parDeploymentLocation string
+param parRg string = 'admin-rg-vm'
+
+// Network connectivity parameters
+param parHubNetworkRg string
+param parHubNetwork string
+param parSubnetAddressSpace string = '10.20.100.0/24'
+param parSubnetName string = 'AdminVmSubnet'
+
+// VM user
+@maxLength(20)
+param parAdminUsername string = 'slzadmin'
+@secure()
+param parAdminPassword string
+
+@description('Timestamp used to date subdeployments.')
+param parTimestamp string = utcNow()
+
+// RG in which VM will deploy
+resource resRg 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+ name: parRg
+ location: parDeploymentLocation
+}
+
+// Creates a subnet in the SLZ hub vnet for the admin workstation to connect to
+module modSubnet '../modules/subnet.bicep' = {
+ name: 'AdminVM-Subnet-${parTimestamp}'
+ scope: resourceGroup(parHubNetworkRg)
+ params: {
+ parHubNetwork: parHubNetwork
+ parSubnetName: parSubnetName
+ parSubnetAddressSpace: parSubnetAddressSpace
+ }
+}
+
+// Creates the VM
+module modWin11Vm '../modules/win11vm.bicep' = {
+ scope: resRg
+ name: 'AdminVM-VM-${parTimestamp}'
+ params: {
+ parDeploymentLocation: parDeploymentLocation
+ parSubnetId: modSubnet.outputs.outSubnetId
+ parAdminUsername: parAdminUsername
+ parAdminPassword: parAdminPassword
+ }
+}
+
+output outVmName string = modWin11Vm.outputs.outVmName
+output outVmResourceId string = modWin11Vm.outputs.outVmResourceId
diff --git a/sovereignApplications/nonConfidential/adminVM/scripts/deploy.ps1 b/sovereignApplications/nonConfidential/adminVM/scripts/deploy.ps1
new file mode 100644
index 0000000..3e3498d
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/scripts/deploy.ps1
@@ -0,0 +1,31 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script deploy the admin virtual machine.
+
+.DESCRIPTION
+- Executes the individual modules to read parameter values and create the admin virtual machine.
+
+#>
+$varParameters = Get-Content -Path .\parameters\adminvm.parameters.json | ConvertFrom-Json
+$varTimestamp = Get-Date -Format FileDateTimeUniversal
+$parDeploymentLocation = $varParameters.parameters.parDeploymentLocation.value
+
+$varVmDeployResult = New-AzDeployment -Name AdminVM-$varTimestamp `
+ -Location $parDeploymentLocation `
+ -TemplateFile ".\adminvm.bicep" `
+ -TemplateParameterFile ".\parameters\adminvm.parameters.json" `
+ -ErrorAction Stop
+
+$varBastion = Get-AzBastion
+$varBastionName = $varBastion.Name
+$varBastionResourceGroupName = $varBastion.ResourceGroupName
+$varBastionSubscriptionId = $varBastion.Id.Substring(15, 36)
+$varVmResourceId = $varVmDeployResult.Outputs.outVmResourceId.value
+
+az login
+az account set --subscription $varBastionSubscriptionId
+az network bastion update --name $varBastionName --resource-group $varBastionResourceGroupName --enable-tunneling true --sku "Standard"
+az network bastion rdp --name $varBastionName --resource-group $varBastionResourceGroupName --target-resource-id $varVmResourceId
+Write-Information ">>> Admin virtual machine deployment Successful" -InformationAction Continue
diff --git a/sovereignApplications/nonConfidential/adminVM/scripts/parameters/adminvm.parameters.json b/sovereignApplications/nonConfidential/adminVM/scripts/parameters/adminvm.parameters.json
new file mode 100644
index 0000000..033a824
--- /dev/null
+++ b/sovereignApplications/nonConfidential/adminVM/scripts/parameters/adminvm.parameters.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parDeploymentLocation": {
+ "value": ""
+ },
+ "parHubNetworkRg": {
+ "value": ""
+ },
+ "parHubNetwork": {
+ "value": ""
+ },
+ "parAdminUsername": {
+ "value": ""
+ }
+ }
+}
diff --git a/sovereignApplications/nonConfidential/storageAccount/README.md b/sovereignApplications/nonConfidential/storageAccount/README.md
new file mode 100644
index 0000000..646c2e5
--- /dev/null
+++ b/sovereignApplications/nonConfidential/storageAccount/README.md
@@ -0,0 +1,3 @@
+# Contoso HR Sample Application - Non-Confidential Storage Account
+
+This sample application is part of the [hrAppWorkload](../../confidential/hrAppWorkload/README.md) confidential sample application and is a Bicep module that an Azure Storage Account with a customer managed key into a target subscription. This is used to verify, if the enforcement of confidentiality policies does work as expected in respective subscriptions.
\ No newline at end of file
diff --git a/sovereignApplications/nonConfidential/storageAccount/parameters/storageAccountCMK.parameters.json b/sovereignApplications/nonConfidential/storageAccount/parameters/storageAccountCMK.parameters.json
new file mode 100644
index 0000000..5b7a2dc
--- /dev/null
+++ b/sovereignApplications/nonConfidential/storageAccount/parameters/storageAccountCMK.parameters.json
@@ -0,0 +1,36 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parTenantId": {
+ "value": ""
+ },
+ "parLocation": {
+ "value": ""
+ },
+ "parKeyVaultName": {
+ "value": ""
+ },
+ "parKeyVaultKeyName": {
+ "value": "",
+ "metadata": {
+ "description": "Name of the customer-managed key in the key vault."
+ }
+ },
+ "parKeyExpirationTime": {
+ "value": 0,
+ "metadata": {
+ "description": "Expiration time of the customer-managed key in the key vault."
+ }
+ },
+ "parStorageAccountName": {
+ "value": ""
+ },
+ "parUserAssignedIdentityName": {
+ "value": "",
+ "metadata": {
+ "description": "Name of the customer-managed key in the key vault."
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/sovereignApplications/nonConfidential/storageAccount/scripts/template.storageAccountCMK.bicep b/sovereignApplications/nonConfidential/storageAccount/scripts/template.storageAccountCMK.bicep
new file mode 100644
index 0000000..1b0cff1
--- /dev/null
+++ b/sovereignApplications/nonConfidential/storageAccount/scripts/template.storageAccountCMK.bicep
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the Azure storage account with managed customer keys.
+DESCRIPTION: This module will create a deployment which will create the Azure storage account, key vaulter and managed customer keys to store and manage data and logs.
+AUTHOR/S: Cloud for Sovereignty
+*/
+param parLocation string = resourceGroup().location
+param parTenantId string = subscription().tenantId
+
+@description('Timestamp with format yyyyMMddTHHmmssZ. Default value set to Execution Timestamp to avoid deployment contention.')
+param parTimestamp string = uniqueString(utcNow())
+
+param parKeyVaultName string ='vault-${parTimestamp}'
+param parKeyVaultKeyName string = 'cmkey'
+param parKeyExpirationTime int = dateTimeToEpoch(dateTimeAdd(utcNow(), 'P1Y'))
+param parStorageAccountName string = 'storage${parTimestamp}'
+param parUserAssignedIdentityName string = 'keyVaultOwner${parTimestamp}'
+
+var varRoleAssignmentName = guid(resourceGroup().id, 'owner','compliance')
+
+@description('Docs on supported role GUIDs can be found here https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles.')
+var varOwnerRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
+
+resource resUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
+ name: parUserAssignedIdentityName
+ location: parLocation
+}
+
+resource resRoleAssignmentWithScope 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: varRoleAssignmentName
+ properties: {
+ roleDefinitionId: varOwnerRoleDefinitionId
+ principalId: resUserAssignedIdentity.properties.principalId
+ principalType: 'ServicePrincipal'
+ }
+}
+
+resource resKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
+ name: parKeyVaultName
+ location: parLocation
+ properties: {
+ sku: {
+ name: 'standard'
+ family: 'A'
+ }
+ enableSoftDelete: true
+ enablePurgeProtection: true
+ enabledForDiskEncryption: true
+ tenantId: parTenantId
+ accessPolicies: [
+ {
+ tenantId: parTenantId
+ permissions: {
+ keys: [
+ 'create'
+ 'get'
+ 'list'
+ 'unwrapKey'
+ 'wrapKey'
+ ]
+ }
+ objectId: resUserAssignedIdentity.properties.principalId
+ }
+ ]
+ }
+}
+
+resource resKeyVaultKey 'Microsoft.KeyVault/vaults/keys@2023-02-01' = {
+ parent: resKeyVault
+ name: parKeyVaultKeyName
+ properties: {
+ attributes: {
+ enabled: true
+ exp: parKeyExpirationTime
+ }
+ keySize: 4096
+ kty: 'RSA'
+ }
+}
+
+resource resStorageAccountWithCMK 'Microsoft.Storage/storageAccounts@2023-01-01' = {
+ name: parStorageAccountName
+ location: parLocation
+ sku: {
+ name: 'Standard_LRS'
+ }
+ kind: 'StorageV2'
+ identity: {
+ type: 'UserAssigned'
+ userAssignedIdentities: {
+ '${resUserAssignedIdentity.id}': {}
+ }
+ }
+ properties: {
+ accessTier: 'Hot'
+ allowBlobPublicAccess: false
+ encryption: {
+ identity: {
+ userAssignedIdentity: resUserAssignedIdentity.id
+ }
+ keySource: 'Microsoft.Keyvault'
+ keyvaultproperties: {
+ keyname: resKeyVaultKey.name
+ keyvaulturi: endsWith(resKeyVault.properties.vaultUri,'/') ? substring(resKeyVault.properties.vaultUri,0,length(resKeyVault.properties.vaultUri)-1) : resKeyVault.properties.vaultUri
+ }
+ requireInfrastructureEncryption: true
+ services: {
+ blob: {
+ enabled: true
+ }
+ }
+ }
+ networkAcls: {
+ defaultAction: 'Deny'
+ }
+ minimumTlsVersion: 'TLS1_2'
+ publicNetworkAccess: 'Disabled'
+ supportsHttpsTrafficOnly: true
+ }
+}
diff --git a/workloadAccelerators/README.md b/workloadAccelerators/README.md
new file mode 100644
index 0000000..efabdac
--- /dev/null
+++ b/workloadAccelerators/README.md
@@ -0,0 +1,8 @@
+# Workload templates for Sovereign Landing Zone
+
+We're introducing two templates:
+
+1. [**Azure Lighthouse template**](./lighthouse/docs/lighthouseAccelerator.md)
+1. [**Azure Confidential Virtual Machine AMD-SNP template**](./confidentialVirtualMachine/docs/cvmAccelerator.md)
+
+You can deploy all applications using the PowerShell and Bicep, and they are compatible with the Sovereign Landing Zone (SLZ). To learn more about the advantages of using these templates, deployment instructions, and usage guidelines, please follow the links provided.
diff --git a/workloadAccelerators/confidentialVirtualMachine/README.md b/workloadAccelerators/confidentialVirtualMachine/README.md
new file mode 100644
index 0000000..a7c3636
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/README.md
@@ -0,0 +1,6 @@
+# Azure Confidential Virtual Machine AMD-SNP template
+
+Follow this link to learn more about this template:
+
+[**Azure Confidential Virtual Machine AMD-SNP template**](./docs/cvmAccelerator.md)
+
diff --git a/workloadAccelerators/confidentialVirtualMachine/docs/cvmAccelerator.md b/workloadAccelerators/confidentialVirtualMachine/docs/cvmAccelerator.md
new file mode 100644
index 0000000..41f08b3
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/docs/cvmAccelerator.md
@@ -0,0 +1,111 @@
+# Azure Confidential Virtual Machine AMD-SNP template
+
+Confidential Virtual Machines (Confidential VMs) are an offering within Azure Confidential Computing, designed for customers with demanding security and confidentiality requirements.
+
+The Azure Confidential Virtual Machine AMD-SNP template offers Confidential VMs based on [AMD processors with SEV-SNP technology](https://learn.microsoft.com/azure/confidential-computing/virtual-machine-solutions-amd). These VMs provide a hardware-enforced boundary to help meet customer security needs. Customers can use confidential VMs for migration without making changes to the code, with the platform protecting their VM's state from being read or modified.
+
+The template requires two prerequisites:
+* Active subscription to deploy this template
+* Owner or write permissions on the subscription in which the confidential VM should be deployed to
+
+There's no hard dependency of [Sovereign Landing Zone (SLZ)](https://github.com/Azure/sovereign-landing-zone), however we have implemented and tested this template under the SLZ confidential management group where existing confidential policies are enforced.
+
+> [!NOTE]
+> Common known issues and FAQ are listed in our [Known Issues and FAQ](faq.md) page. If you cannot find an answer in the known issues page, then please log issues to [GitHub issues](https://github.com/Azure/cloud-for-sovereignty-quickstarts/issues)
+
+## Deploy Azure Confidential Virtual Machine AMD-SNP template
+
+1. Open **PowerShell 7.x**.
+
+1. Clone repo [cloud for sovereignty apps](https://github.com/Azure/cloud-for-sovereignty-quickstarts)
+
+ ``` powershell
+ git clone https://github.com/Azure/cloud-for-sovereignty-quickstarts
+ ```
+
+1. Create a [subscription](../../../sovereignApplications/confidential/hrAppWorkload/docs/03.1-subscription.md) if you don't have an existing one.
+
+1. Run the following commands to authenticate to Azure and to set the subscription for which you want this deployment to be hosted.
+
+ ``` powershell
+ Login-AzAccount
+
+ Connect-AzAccount -Subscription
+ ```
+
+1. Navigate to `cloud-for-sovereignty-quickstarts\workloadAccelerators\confidentialVirtualMachine\scripts\parameters\` and open `confidentialVirtualMachine.parameters` in a text editor, for example VS Code.
+
+ ``` powershell
+ cd .\cloud-for-sovereignty-quickstarts\workloadAccelerators\confidentialVirtualMachine\scripts\parameters\
+ ```
+
+ ``` powershell
+ code .
+ ```
+
+1. Update the parameters and save the file. Use table [Parameters](#parameters) for assistance.
+
+1. Change directory to scripts and run deployment PowerShell command to deploy the template.
+
+ ``` powershell
+ cd ..
+ .\confidentialVirtualMachineAccelerator.ps1
+ ```
+
+1. A successful deployment finishes with the following statements:
+
+ ``` powershell
+ >>> Confidential VM deployment successful
+ ```
+
+1. Confirm deployment completion by connecting to the Azure Portal
+ * Connect to Azure Portal and search for your subscription in the main "Search resources, services, and docs"
+ * Type your subscription name that you have provided above
+ * Once you have identified your subscription, verify in `Deployments` the last deployment succeeded.
+ * Navigate to Resource Groups and find {parDeploymentPrefix}-rg
+ * Inspect all resources created.
+
+1. Confirm deployment completion by connecting to the Confidential VM
+ * Connect to your VM through the *Connect/Bastion* link on its page in the Azure Portal.
+ * Find the VM in the Azure portal, click the *Connect/Go to Bastion*, then enter the login credentials for the VM and selet *Connect*. See [Connect to VM Screen 1](../../../sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM1.png) and [Connect to VM Screen 2](../../../sovereignApplications/confidential/hrAppWorkload/media/connect-to-VM2.png) for illustration. You provided the admin username in the parameters file `parAdminUsername`, and you provided the password in the parameter `parAdminPasswordOrKey`
+
+## Parameters
+
+Descriptions and accepted values for all parameters within the ``confidentialVirtualMachine.parameters.json`` file are described as follows:
+
+We provide default values for most available parameters, but you must set the parameters that are required. Refer to the table or to parameter definitions in the [bicep script](../scripts/confidentialVirtualMachineApp.bicep) for allowed values. All parameters marked with ``*`` are required. Per JSON syntax, strings are delimited by quotes, whereas numeric values and boolean values aren't.
+
+### Required parameters
+
+| Parameter |Description | Examples | Default value |
+|---------------------|-----------------------------------------|----------|--------|
+| parDeploymentPrefix* | Prefix that is added to all resources created by this deployment. | Five characters or less, lowercase, for example: mcfs | |
+| parDeploymentLocation* | Location of the deployment. | for example: northeurope | |
+| parHubNetworkResourceId* | Resource ID of the SLZ's hub virtual network from the *%slzprefix%-connectivity* subscription. For more information of where to retrieve this parameter value, see [image](../../../sovereignApplications/confidential/hrAppWorkload/media/slz-virtual-network.png). Navigate to your SLZ's connectivity subscription -> Resource Groups. Select the Resource Group, which isn't NetworkWatcherRG. On the Resources page, select the virtual network resource and go to its Properties page. | /subscriptions/`/resourceGroups /rg-aslz-hub-network-eastus/ providers/Microsoft.Network/ virtualNetworks/hub-aslz-eastus | "" |
+ | parAdminPasswordOrKey* | Password or public key (see option `parAuthenticationType`) for the admin user created on Confidential VM. If password, must fulfill password complexity requirements set by SLZ policy. | P4$$w0rd!!! | |
+ | parVnetAddressPrefix* | IP address space of the virtual network created for this workload, in CIDR notation. Must be unique across all vnets peered against the SLZ hub vnet as referenced above. For example, if SLZ's parHubNetworkAddressPrefix is 10.20.0.0/16, then this parameter can't be the same, for example it can be 10.21.0.0/16 | 10.21.0.0/16 | |
+
+
+### Optional parameters
+
+| Parameter |Description | Examples | Default value |
+|---------------------|-----------------------------------------|----------|--------|
+| parVmName | Name of the Confidential VM to be created. The deployed resource has a unique name suffix appended to prevent namespace clashes. | cvm | {parDeploymentPrefix} -vm-{unique-suffix} | |
+| parAdminUsername | Admin user name for the virtual machine | test-admin | {parDeploymentPrefix}-admin |
+| parVmSize | Size of the Confidential VM to be created. For more information, see [supported sizes](https://learn.microsoft.com/azure/virtual-machines/dcasv5-dcadsv5-series) | Standard_DC2as_v5 | Standard_DC2as_v5 |
+| parOsImageName | OS image for the virtual machine. Allowed values are: 'Windows Server 2022 Gen 2', 'Windows Server 2019 Gen 2', 'Ubuntu 22.04 LTS Gen 2', 'Ubuntu 20.04 LTS Gen 2'| Ubuntu 22.04 LTS Gen 2 | Ubuntu 22.04 LTS Gen 2 |
+| parAttestationProvidersName | Attestation provider name. The deployed resource has a unique name suffix appended to prevent namespace clashes. | testattp | {parDeploymentPrefix} attp{unique-suffix} |
+| parKeyVaultName | Azure Key Vault name to hold the keys for the Disk Encryption Set used for the VM disks. The deployed resource has a unique name suffix appended to prevent namespace clashes. | test-kv | {parDeploymentPrefix}-kv |
+| parManagedIdentityName | Name of the User Assigned Managed Identity used in some RBAC scenarios (for example, for the Disk Encryption Set). | test-id | {parDeploymentPrefix}-id |
+| parVirtualNetworkName | Virtual network name of this VM| - | {parDeploymentPrefix}-vnet |
+| parTags | List of tags to be applied to some resources. | {'product': 'cvm-template'} | {'product': 'cvm-template'} |
+
+### Adding parameters to override optional ones
+
+In case you want to override an available [optional parameter](#optional-parameters) from the table with a value that isn't in the parameter file, just add a JSON fragment corresponding to the value already there, for example:
+
+``` json
+ "parAdminUsername": {
+ "value": "AdminUser"
+ },
+```
diff --git a/workloadAccelerators/confidentialVirtualMachine/docs/faq.md b/workloadAccelerators/confidentialVirtualMachine/docs/faq.md
new file mode 100644
index 0000000..fa0c5b4
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/docs/faq.md
@@ -0,0 +1,10 @@
+# Azure Confidential Virtual Machine AMD-SNP template - Frequently Asked Questions
+
+This document answers the most common questions related to the Azure Confidential Virtual Machine AMD-SNP template
+
+1. Deployment fails with error `Code: InUseSubnetCannotBeUpdated`, see image for exact [error message](media/in-use-subnet.png)
+ * **Reason**: This error message indicates that the subnet parVnetAddressPrefix you've chosen for this app is in use by other application and you need to choose another prefix.
+ * **Resolution**: Delete the entire resource group that was created and redeploy the application with a new parVnetAddressPrefix which doesn't collide with existing subnets.
+
+To report issues or get support, please submit a ticket through [GitHub Issues](https://github.com/Azure/cloud-for-sovereignty-quickstarts/issues)
+
diff --git a/workloadAccelerators/confidentialVirtualMachine/docs/media/in-use-subnet.png b/workloadAccelerators/confidentialVirtualMachine/docs/media/in-use-subnet.png
new file mode 100644
index 0000000..777bfda
Binary files /dev/null and b/workloadAccelerators/confidentialVirtualMachine/docs/media/in-use-subnet.png differ
diff --git a/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachine.bicep b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachine.bicep
new file mode 100644
index 0000000..7e05642
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachine.bicep
@@ -0,0 +1,307 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the confidential virtual machine bicep script.
+DESCRIPTION: This module will create a deployment which will create the confidential virtual machine.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Location for all resources.')
+param parDeploymentLocation string
+
+@description('List of tags to be applied to resources.')
+param parTags object
+
+@description('Key vault name')
+param parKeyVaultName string
+
+@description('Virtual network that the Confidential VM connects to.')
+param parVirtualNetworkName string
+
+@description('The name of the Confidential VM (resource and host name).')
+param parVmName string
+
+@description('Is the AMD Confidential VM.')
+param parIsAMDVM bool = true
+
+@description('The size of the virtual machine')
+@allowed([
+ 'Standard_DC2s_v2'
+ 'Standard_DC2as_v5'
+ 'Standard_DC4as_v5'
+])
+param parVmSize string = 'Standard_DC2as_v5'
+
+@description('OS image for the virtual Machine')
+@allowed([
+ 'Windows Server 2022 Gen 2'
+ 'Windows Server 2019 Gen 2'
+ 'Ubuntu 22.04 LTS Gen 2'
+ 'Ubuntu 20.04 LTS Gen 2'
+])
+param parOsImageName string
+
+@description('OS disk type of the VM.')
+@allowed([
+ 'Premium_LRS'
+ 'Standard_LRS'
+ 'StandardSSD_LRS'
+])
+param parOsDiskType string = 'StandardSSD_LRS'
+
+@description('Username for the virtual machine.')
+param parAdminUsername string
+
+@description('Password for the virtual machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
+@secure()
+param parAdminPasswordOrKey string
+
+@description('Type of authentication to use on the virtual machine. SSH Public key is recommended.')
+@allowed([
+ 'sshPublicKey'
+ 'password'
+])
+param parAuthenticationType string
+
+@description('Boolean value indicating whether the VM supports secured boot, e.g., true.')
+param parSecureBoot bool = true
+
+@description('Boolean value indicating whether the VM is using virtualized version of a hardware Trusted Platform Module,e.g., true.')
+param parVTPM bool = true
+
+@description('Disk Encryption Set Id.')
+param parDiskEncryptionSetId string
+
+@description('Virtual Machine Key Id')
+param parVmkeyId string
+
+@description('Virtual machine Properties/userData block. Carries a string that the VM can access thorugh the IMDS service, see https://learn.microsoft.com/en-us/azure/virtual-machines/user-data.')
+param parVmUserData string
+
+@description('Attestation Provider URI to attest to.')
+param parAttestationUri string
+
+@description('Attestation providers name')
+param parAttestationProvidersName string
+
+var parDiskName = '${parVmName}-osDisk'
+var varDataDiskName = '${parVmName}-dataDisk'
+var varImageList = {
+ 'Windows Server 2022 Gen 2': {
+ publisher: 'microsoftwindowsserver'
+ offer: 'windowsserver'
+ sku: '2022-datacenter-smalldisk-g2'
+ version: 'latest'
+ }
+ 'Windows Server 2019 Gen 2': {
+ publisher: 'microsoftwindowsserver'
+ offer: 'windowsserver'
+ sku: '2019-datacenter-smalldisk-g2'
+ version: 'latest'
+ }
+ 'Ubuntu 22.04 LTS Gen 2': {
+ publisher: 'Canonical'
+ offer: '0001-com-ubuntu-confidential-vm-jammy'
+ sku: '22_04-lts-cvm'
+ version: 'latest'
+ }
+ 'Ubuntu 20.04 LTS Gen 2': {
+ publisher: 'Canonical'
+ offer: '0001-com-ubuntu-confidential-vm-focal'
+ sku: '20_04-lts-cvm'
+ version: 'latest'
+ }
+}
+var varImageReference = varImageList[parOsImageName]
+var varAscReportingEndpoint = resAttestationProvider.properties.attestUri
+var varDisableAlerts = 'false'
+var varExtensionName = 'GuestAttestation'
+var varExtensionPublisher = varIsWindows ? 'Microsoft.Azure.Security.WindowsAttestation' : 'Microsoft.Azure.Security.LinuxAttestation'
+var varExtensionVersion = '1.0'
+var varMaaTenantName = 'GuestAttestation'
+var varUseAlternateToken = 'false'
+var varIsWindows = contains(parOsImageName, 'Windows')
+var varLinuxConfiguration = {
+ disablePasswordAuthentication: 'true'
+ ssh: {
+ publicKeys: [
+ {
+ keyData: parAdminPasswordOrKey
+ path: '/home/${parAdminUsername}/.ssh/authorized_keys'
+ }
+ ]
+ }
+}
+var varWindowsConfiguration = {
+ enableAutomaticUpdates: 'true'
+ provisionVmAgent: 'true'
+}
+
+var varEncryptionOperation = 'EnableEncryption'
+var varKeyEncryptionAlgorithm = 'RSA-OAEP'
+var varKeyVaultResourceID = resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults/', parKeyVaultName)
+
+// Existing resources stood up by common module or provided by Azure
+resource resKeyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
+ name: parKeyVaultName
+}
+resource resAttestationProvider 'Microsoft.Attestation/attestationProviders@2021-06-01-preview' existing = {
+ name: parAttestationProvidersName
+}
+
+// The built-in Key Vault Administrator role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator
+resource resKeyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
+}
+
+// The built-in Attestation Reader role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#attestation-reader
+resource resAttestationProviderAttestationReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: 'fd1bd22b-8476-40bc-a0bc-69b95687b9f3'
+}
+
+// Virtual network and subnet from common module
+resource resVnet 'Microsoft.Network/virtualNetworks@2022-07-01' existing = {
+ name: parVirtualNetworkName
+}
+
+// Virtual machine
+resource resVm 'Microsoft.Compute/virtualMachines@2023-03-01' = {
+ name: parVmName
+ location: parDeploymentLocation
+ tags: parTags
+ identity: {
+ type: 'SystemAssigned'
+ }
+ properties: {
+ userData: base64(parVmUserData)
+ hardwareProfile: {
+ vmSize: parVmSize
+ }
+ osProfile: {
+ computerName: length(parVmName) <= 15 ? parVmName : replace(take(parVmName,15),'-','') // If vm name is too long, reduce for compliance with Windows naming.
+ adminUsername: parAdminUsername
+ adminPassword: parAdminPasswordOrKey
+ linuxConfiguration: ((parAuthenticationType == 'password') ? null : varLinuxConfiguration)
+ windowsConfiguration: (varIsWindows ? varWindowsConfiguration : null)
+ }
+ storageProfile: {
+ osDisk: {
+ name: parDiskName
+ createOption: 'FromImage'
+ managedDisk: {
+ securityProfile: {
+ securityEncryptionType: 'VMGuestStateOnly'
+ }
+ storageAccountType: parOsDiskType
+ diskEncryptionSet: {
+ id: parDiskEncryptionSetId
+ }
+ }
+ }
+ dataDisks: [
+ {
+ name: varDataDiskName
+ diskSizeGB: 128
+ lun: 0
+ createOption: 'Empty'
+ managedDisk: {
+ storageAccountType: 'Premium_LRS'
+ diskEncryptionSet: {
+ id: parDiskEncryptionSetId
+ }
+ }
+ }
+ ]
+ imageReference: varImageReference
+ }
+ networkProfile: {
+ networkInterfaces: [
+ {
+ id: resNIC.id
+ }
+ ]
+ }
+ securityProfile: {
+ uefiSettings: {
+ secureBootEnabled: parSecureBoot
+ vTpmEnabled: parVTPM
+ }
+ securityType: 'ConfidentialVM'
+ }
+ }
+}
+
+// Assign the Confidential VM service principal full rights on key vault. Could probably be tightened.
+resource resKeyVaultAdminRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parVmName, resKeyVaultAdministratorRoleDefinition.id)
+ scope: resKeyVault
+ properties: {
+ principalId: resVm.identity.principalId
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+// Assign the Confidential VM service principal reader rights on attestation provider.
+resource resAttestationProviderAttestationReaderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(resKeyVault.id, parVmName, resAttestationProviderAttestationReaderRoleDefinition.id)
+ scope: resAttestationProvider
+ properties: {
+ principalId: resVm.identity.principalId
+ roleDefinitionId: resAttestationProviderAttestationReaderRoleDefinition.id
+ principalType: 'ServicePrincipal'
+ }
+}
+
+// VM NIC
+resource resNIC 'Microsoft.Network/networkInterfaces@2022-07-01' = {
+ name: 'nic-${parVmName}'
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ ipConfigurations: [
+ {
+ name: 'ipconfig'
+ properties: {
+ privateIPAllocationMethod: 'Dynamic'
+ subnet: {
+ id: resVnet.properties.subnets[0].id
+ }
+ }
+ }
+ ]
+ }
+}
+
+resource vmName_extension 'Microsoft.Compute/virtualMachines/extensions@2022-08-01' = if (parVTPM && parSecureBoot && parIsAMDVM == false) {
+ parent: resVm
+ name: varExtensionName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ publisher: varExtensionPublisher
+ type: varExtensionName
+ typeHandlerVersion: varExtensionVersion
+ autoUpgradeMinorVersion: true
+ forceUpdateTag: '1.0'
+ settings: {
+ maaEndpoint: parAttestationUri
+ maaTenantName: varMaaTenantName
+ ascReportingEndpoint: varAscReportingEndpoint
+ useAlternateToken: varUseAlternateToken
+ disableAlerts: varDisableAlerts
+ EncryptionOperation: varEncryptionOperation
+ KeyVaultURL: reference(varKeyVaultResourceID, '2022-07-01').vaultUri
+ KeyVaultResourceId: varKeyVaultResourceID
+ KeyEncryptionKeyURL: reference(parVmkeyId, '2022-07-01', 'Full').properties.keyUriWithVersion
+ KekVaultResourceId: varKeyVaultResourceID
+ KeyEncryptionAlgorithm: varKeyEncryptionAlgorithm
+ VolumeType: 'All'
+ ResizeOSDisk: false
+ }
+ }
+}
+
+output outVmName string = resVm.name
+output outVmServicePrincipalObjectId string = resVm.identity.principalId
diff --git a/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineAccelerator.ps1 b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineAccelerator.ps1
new file mode 100644
index 0000000..d7d43e3
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineAccelerator.ps1
@@ -0,0 +1,134 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script serves as the overarching script to deploy the workload template either in its entirety or in a piecemeal manner the below individual modules.
+
+.DESCRIPTION
+- Executes the individual modules - hr app sample workload
+
+Prerequisites:
+
+Connect-AzAccount -Subscription %SUBSCRIPTION_ID%
+Add-SqlAzureAuthenticationContext -Interactive
+#>
+
+using namespace System.Collections
+param (
+ [Parameter(Mandatory = $false, Position = 0)]
+ [string] $parRootDeploymentLocation = "eastus",
+ [Parameter(Mandatory = $false, Position = 0)]
+ [bool] $parAttendedLogin = $true
+)
+
+#reference to common scripts
+. "..\..\..\common\common.ps1"
+
+# Retry logic parameters (in case of transient errors)
+$varMaxTransientErrorRetryAttempts = 3
+$varRetryWaitTime = 60
+
+#bicep files
+$varConfidentialVirtualMachineApp = 'confidentialVirtualMachineApp.bicep'
+$varAppParametersFile = './parameters/confidentialVirtualMachine.parameters.json'
+
+<#
+.DESCRIPTION
+ Deploys confidential virtual machine template Azure resources.
+#>
+function New-AppResourceDeployment {
+ param(
+ [Parameter(Mandatory = $True, Position = 0)]
+ [string] $parDeployingUserObjectId,
+ [Parameter(Mandatory = $False, Position = 2)]
+ [bool] $parIsValidation = $False
+ )
+
+ $varDonotRetryErrorCodes = Get-DonotRetryErrorCodes '../../../common/const/doNotRetryErrorCodes.json'
+ $varLoopCounter = 0
+ while ($varLoopCounter -lt $varMaxTransientErrorRetryAttempts) {
+ try {
+ Write-Information ">>> Starting deployment of confidential virtual machine template Azure resources." -InformationAction Continue
+ $varTimestamp = Get-Date -Format FileDateTimeUniversal
+ $varAppDeployment = $null
+ $varDeploymentName = "App-$varTimestamp"
+ if ($parIsValidation) {
+ $varAppDeployment = Test-AzDeployment `
+ -Name $varDeploymentName `
+ -Location $parRootDeploymentLocation `
+ -TemplateFile $varConfidentialVirtualMachineApp `
+ -TemplateParameterFile $varAppParametersFile `
+ -parDeployingUserObjectId $parDeployingUserObjectId `
+
+ if ($varAppDeployment.Count -gt 0) {
+ Write-Error $varAppDeployment[0].Message -ErrorAction Stop
+ }
+
+ Write-Information ">>> Successfully validated input parameter file." -InformationAction Continue
+ }
+ else {
+ $varAppDeployment = New-AzDeployment `
+ -Name $varDeploymentName `
+ -Location $parRootDeploymentLocation `
+ -TemplateFile $varConfidentialVirtualMachineApp `
+ -TemplateParameterFile $varAppParametersFile `
+ -parDeployingUserObjectId $parDeployingUserObjectId `
+
+ if (!$varAppDeployment -or $varAppDeployment.ProvisioningState -eq "Failed") {
+ Write-Error "Error while executing confidential virtual machine template deployment." -ErrorAction Stop
+ }
+ else {
+ Write-Information ">>> Successfully deployed confidential virtual machine template Azure resources." -InformationAction Continue
+ }
+ }
+
+ return $varAppDeployment
+ }
+ catch {
+ $varLoopCounter++
+ $varException = $_.Exception
+ $varErrorDetails = $_.ErrorDetails
+ $varTrace = $_.ScriptStackTrace
+
+ if ($null -ne $varException) {
+ $errorCode = $varAppDeployment[0].Code
+ }
+
+ Write-Error "$varException \n $varErrorDetails \n $varTrace" -ErrorAction Continue
+
+ if ($varDonotRetryErrorCodes -notcontains $errorCode -and $varLoopCounter -lt $varMaxTransientErrorRetryAttempts) {
+ Write-Information ">>> A deployment error occured, see above. The error may be transient. Retrying deployment after waiting for $varRetryWaitTime seconds." -InformationAction Continue
+ Start-Sleep -Seconds $varRetryWaitTime
+ }
+ else {
+ if ($varLoopCounter -eq $varMaxTransientErrorRetryAttempts) {
+ Write-Information ">>> Maximum number of retry attempts reached. Cancelling deployment." -InformationAction Continue
+ }
+ Write-Error ">>> Error occurred in confidential virtual machine template. Please try after addressing the error : $varException \n $varErrorDetails \n $varTrace" -ErrorAction Stop
+ }
+ }
+ }
+}
+
+# Begin execution
+# Preliminaries
+$varAzContext = Get-AzContext
+$varAzContextUserObjectId = $varAzContext.Account.ExtendedProperties.HomeAccountId.Split('.')[0]
+
+if ($parAttendedLogin) {
+ # Confirm Prerequisites
+ Confirm-Prerequisites
+}
+
+# Validate the app resource deployment script with the values from parameter file.
+$varAppDeployment = New-AppResourceDeployment `
+ -parDeployingUserObjectId $varAzContextUserObjectId `
+ -parIsValidation $True
+
+Register-Compute
+# Create the App resource deployment in Azure and parse the returned object to retrieve outputs
+$varAppDeployment = New-AppResourceDeployment `
+ -parDeployingUserObjectId $varAzContextUserObjectId
+
+# Final message for successful deployment
+Write-Information ">>> Confidential VM deployment Successful" -InformationAction Continue
diff --git a/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineApp.bicep b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineApp.bicep
new file mode 100644
index 0000000..7511f7c
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineApp.bicep
@@ -0,0 +1,159 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the confidential virtual machine template bicep script.
+DESCRIPTION: This module will create a deployment which will create the confidential virtual machine template with its required infrastructures.
+AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'subscription'
+
+@description('The prefix that will be added to all resources created by this deployment.')
+@minLength(2)
+@maxLength(5)
+param parDeploymentPrefix string
+
+@description('Deployment location for all resources.')
+@allowed([
+ 'eastus'
+ 'westus'
+ 'northeurope'
+ 'westeurope'
+])
+param parDeploymentLocation string
+
+@description('Timestamp used to date subdeployments.')
+param parTimestamp string = utcNow()
+
+@description('The Resource Group in which this sample workload will be deployed.')
+param parResourceGroupName string = '${parDeploymentPrefix}-rg'
+
+@description('Object ID of the user executing this deployment (used for RBAC assignments supporting later scripted steps).')
+param parDeployingUserObjectId string
+
+@maxLength(10)
+@description('The Attestation Provider name, e.g., prefixattp. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parAttestationProvidersName string = '${parDeploymentPrefix}attp'
+
+@description('Name of Key Vault holding keys for disk encryption of VM disks, e.g., myprefix-kv. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parKeyVaultName string = '${parDeploymentPrefix}-kv'
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string = '${parDeploymentPrefix}-id'
+
+@description('Key expiry date in seconds since 1970-01-01T00:00:00Z. Defaults to December 31, 2024.')
+param parKeyExpirationDate int = dateTimeToEpoch('20241231T235959Z')
+
+@description('Resource ID of the SLZ\'s hub vnet in the -connectivity subscription.')
+param parHubNetworkResourceId string
+
+@description('IP address space of the VNET created for this workload, in CIDR notation.')
+param parVnetAddressPrefix string
+
+@description('IP address space of the VNET subnet created for this workload, in CIDR notation.')
+param parSubnetAddressPrefix string = parVnetAddressPrefix
+
+@description('Name of the Confidential Virtual Machine to be created, e.g., myprefix-vm. Note that deployed resource will have a unique name suffix appended to prevent namespace clashes.')
+param parVmName string = '${parDeploymentPrefix}-rg'
+
+@description('Size of the Confidential Virtual Machine to be created. See link for supported sizes https://learn.microsoft.com/en-us/azure/virtual-machines/dcasv5-dcadsv5-series')
+param parVmSize string = 'Standard_DC2as_v5'
+
+@description('OS image for the virtual Machine')
+param parOsImageName string = 'Ubuntu 22.04 LTS Gen 2'
+
+@description('Username for the virtual machine.')
+param parAdminUsername string = '${parDeploymentPrefix}-admin'
+
+@description('Password for the virtual machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
+@secure()
+param parAdminPasswordOrKey string
+
+@description('Type of authentication to use on the virtual machine. SSH Public key is recommended.')
+@allowed([
+ 'sshPublicKey'
+ 'password'
+])
+param parAuthenticationType string = 'password'
+
+@description('The virtual network name for this deployment, e.g., myprefix-vnet.')
+param parVirtualNetworkName string = '${parDeploymentPrefix}-vnet'
+
+@description('The network security group used for virtual network, e.g., myprefix-nsg.')
+param parNetworkSecurityGroupName string = '${parDeploymentPrefix}-nsg'
+
+@description('List of tags to be applied to resources.')
+param parTags object = { product: 'cvm-accelerator' }
+
+@description('Suffix used to add unique identity to globally namespaced resources.')
+var varUniqueSuffix = uniqueString(resResourceGroup.id, parDeploymentLocation)
+
+@description('Key vault name with unique suffix.')
+var varUniqueKeyVaultName = '${parKeyVaultName}-${varUniqueSuffix}'
+
+@description('Virtual Machine name with unique suffix.')
+var varUniqueVmName = '${parVmName}-${varUniqueSuffix}'
+
+@description('Attestation Provider name with unique suffix.')
+var varUniqueAttestationProviderName = '${parAttestationProvidersName}${varUniqueSuffix}'
+
+resource resResourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+ location: parDeploymentLocation
+ name: parResourceGroupName
+ tags: parTags
+}
+
+module modManagedIdentity '../../../common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep' = {
+ name: 'App-Managed-Identity-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parLocation: parDeploymentLocation
+ parName: parManagedIdentityName
+ parTags: parTags
+ }
+}
+
+module modCommonInfra 'confidentialVirtualMachineInfra.bicep' = {
+ name: 'CVMApp-Workload-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parManagedIdentityName: modManagedIdentity.outputs.outName
+ parAttestationProvidersName: varUniqueAttestationProviderName
+ parKeyVaultName: varUniqueKeyVaultName
+ parDeploymentLocation: parDeploymentLocation
+ parVirtualNetworkName: parVirtualNetworkName
+ parNetworkSecurityGroupName: parNetworkSecurityGroupName
+ parTags: parTags
+ parVnetAddressPrefix: parVnetAddressPrefix
+ parSubnetAddressPrefix: parSubnetAddressPrefix
+ parHubNetworkResourceId: parHubNetworkResourceId
+ parKeyVaultOrMhsmAdminObjectId: parDeployingUserObjectId
+ parAttestationContributorObjectId: parDeployingUserObjectId
+ parKeyExpirationDate: parKeyExpirationDate
+ }
+}
+
+module modVM 'confidentialVirtualMachine.bicep' = {
+ name: 'App-Workload-CVM-${parTimestamp}'
+ scope: resResourceGroup
+ params: {
+ parTags: parTags
+ parKeyVaultName: varUniqueKeyVaultName
+ parDiskEncryptionSetId: modCommonInfra.outputs.outDiskEncryptionSetId
+ parVmkeyId: modCommonInfra.outputs.outVmkeyId
+ parVmName: varUniqueVmName
+ parVmSize: parVmSize
+ parOsImageName: parOsImageName
+ parAdminUsername: parAdminUsername
+ parAdminPasswordOrKey: parAdminPasswordOrKey
+ parAuthenticationType: parAuthenticationType
+ parDeploymentLocation: parDeploymentLocation
+ parAttestationUri: modCommonInfra.outputs.outAttestationProviderAttestUri
+ parAttestationProvidersName: varUniqueAttestationProviderName
+ parVirtualNetworkName: parVirtualNetworkName
+ parVmUserData: '{}'
+ }
+}
+
+output outResourceGroupName string = resResourceGroup.name
+output outAttestationProviderName string = modCommonInfra.outputs.outAttestationProviderName
+output outVmName string = modVM.outputs.outVmName
diff --git a/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineInfra.bicep b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineInfra.bicep
new file mode 100644
index 0000000..74b172e
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/scripts/confidentialVirtualMachineInfra.bicep
@@ -0,0 +1,292 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+SUMMARY: Module to deploy the azure infrastructures for confidential virtual machine template bicep script.
+DESCRIPTION: This module will create a deployment which will create the required infrastructures of the confidential virtual machine template.
+AUTHOR/S: Cloud for Sovereignty
+*/
+@description('Location for all resources.')
+@allowed([
+ 'eastus'
+ 'westus'
+ 'northeurope'
+ 'westeurope'
+])
+param parDeploymentLocation string
+
+@description('Timestamp used to date subdeployments.')
+param parTimestamp string = utcNow()
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string
+
+@description('Object ID for the Azure AD user who is set as Key Vault Administrator on Key Vault or Managed HSM.')
+param parKeyVaultOrMhsmAdminObjectId string
+
+@description('Object ID for the Azure AD user who is set as Attestation Contributor.')
+param parAttestationContributorObjectId string
+
+@description('Attestation providers name')
+param parAttestationProvidersName string
+
+@description('Key vault name')
+param parKeyVaultName string
+
+@description('Name of the virtual network')
+param parVirtualNetworkName string
+
+@description('Name of the network security group')
+param parNetworkSecurityGroupName string
+
+@description('Resource tags')
+param parTags object
+
+@description('The key size in bits. For example: 2048, 3072, or 4096 for RSA.')
+@allowed([
+ 2048
+ 3072
+ 4096
+])
+param parKeySize int = 2048
+
+@description('The type of key to create')
+@allowed([
+ 'EC'
+ 'EC-HSM'
+ 'RSA'
+ 'RSA-HSM'
+])
+param parKty string = 'RSA'
+
+@description('Name of the key created for use with VM Disk Encryption Set.')
+param parVmDiskEncryptionKeyName string = 'VmDiskEncryptionKey'
+
+@description('Expiry date in seconds since 1970-01-01T00:00:00Z.')
+param parKeyExpirationDate int
+
+@description('IP address space of the VNET created for this workload, in CIDR notation.')
+param parVnetAddressPrefix string
+
+@description('IP address space of the VNET child subnet created for this workload, in CIDR notation.')
+param parSubnetAddressPrefix string
+
+@description('Resource ID of the SLZ\'s hub vnet in the -connectivity subscription.')
+param parHubNetworkResourceId string
+
+var varSubnetName = 'subnet'
+var varDiskEncryptionSetName = 'diskEncryptionSetVM'
+
+var varRoleIdMapping = {
+ KeyVaultCryptoOfficer: '14b46e9e-c2b7-41b4-b07b-48a6ebf60603'
+ KeyVaultAdministrator: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
+ AttestationContributor: 'bbf86eb8-f7b4-4cce-96e4-18cddf81d86e'
+}
+
+// These computed values are used by the Peering module to connect hub->spoke vnet
+var varHubNetworkSubscription = split(parHubNetworkResourceId,'/')[2]
+var varHubNetworkResourceGroup = split(parHubNetworkResourceId, '/')[4]
+var varHubNetworkName = split(parHubNetworkResourceId, '/')[8]
+
+// The built-in Key Vault Administrator role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-administrator
+resource resKeyVaultAdministratorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: varRoleIdMapping.KeyVaultAdministrator
+}
+
+// The built-in Attestation Contributor role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#attestation-contributor
+resource resAttestationContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
+ scope: subscription()
+ name: varRoleIdMapping.AttestationContributor
+}
+
+resource resManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
+ name: parManagedIdentityName
+}
+
+resource resAttestationProvider 'Microsoft.Attestation/attestationProviders@2021-06-01' = {
+ name: parAttestationProvidersName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ }
+}
+
+// Assign the RBAC Attributon Contributor role to the newly minted Attestation Provider.
+resource resAttestationContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: resAttestationProvider
+ name: guid(resAttestationProvider.id, parAttestationContributorObjectId, varRoleIdMapping.AttestationContributor)
+ properties: {
+ roleDefinitionId: resAttestationContributorRoleDefinition.id
+ principalId: parAttestationContributorObjectId
+ }
+}
+
+// Attestation Provider Private Endpoint
+var varAttestationPrivateEndpointName = 'endpoint-${parAttestationProvidersName}'
+resource resAttestationProviderEndpoint 'Microsoft.Network/privateEndpoints@2022-07-01' = {
+ name: varAttestationPrivateEndpointName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ privateLinkServiceConnections: [
+ {
+ name: varAttestationPrivateEndpointName
+ properties: {
+ privateLinkServiceId: resAttestationProvider.id
+ groupIds: [
+ 'standard'
+ ]
+ }
+ }
+ ]
+ subnet: {
+ id: resVirtualNetwork.properties.subnets[0].id
+ }
+ customNetworkInterfaceName: 'nic-${varAttestationPrivateEndpointName}'
+ }
+}
+
+resource keyvault 'Microsoft.KeyVault/vaults@2022-07-01' = {
+ name: parKeyVaultName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ enableRbacAuthorization: true
+ enableSoftDelete: true
+ enablePurgeProtection: true
+ enabledForDeployment: true
+ enabledForDiskEncryption: true
+ enabledForTemplateDeployment: true
+ tenantId: tenant().tenantId
+ accessPolicies: []
+ sku: {
+ family: 'A'
+ name: 'standard'
+ }
+ networkAcls: {
+ defaultAction: 'Allow'
+ bypass: 'AzureServices'
+ }
+ }
+}
+
+resource resVmDiskEncryptionKey 'Microsoft.KeyVault/vaults/keys@2022-07-01' = {
+ parent: keyvault
+ name: parVmDiskEncryptionKeyName
+ tags: parTags
+ properties: {
+ kty: parKty
+ keySize: parKeySize
+ attributes: {
+ exp: parKeyExpirationDate
+ }
+ }
+ dependsOn: [
+ KeyVaultAdminForManagedIdentity
+ ]
+}
+
+resource diskEncryptionSet 'Microsoft.Compute/diskEncryptionSets@2022-03-02' = {
+ name: varDiskEncryptionSetName
+ location: parDeploymentLocation
+ tags: parTags
+ identity: {
+ type: 'UserAssigned'
+ userAssignedIdentities: {
+ '${resManagedIdentity.id}': {
+ }
+ }
+ }
+ properties: {
+ activeKey: {
+ sourceVault: {
+ id: keyvault.id
+ }
+ keyUrl: resVmDiskEncryptionKey.properties.keyUriWithVersion
+ }
+ encryptionType: 'EncryptionAtRestWithPlatformAndCustomerKeys'
+ rotationToLatestKeyVersionEnabled: true
+ }
+ dependsOn: [
+ KeyVaultAdminForManagedIdentity
+ ]
+}
+
+resource KeyVaultAdminForManagedIdentity 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: keyvault
+ name: guid(keyvault.id, resManagedIdentity.id, varRoleIdMapping.KeyVaultAdministrator)
+ properties: {
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalId: resManagedIdentity.properties.principalId
+ principalType: 'ServicePrincipal'
+ }
+}
+
+resource KeyVaultAdmin 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ scope: keyvault
+ name: guid(keyvault.id, parKeyVaultOrMhsmAdminObjectId, varRoleIdMapping.KeyVaultAdministrator)
+ properties: {
+ roleDefinitionId: resKeyVaultAdministratorRoleDefinition.id
+ principalId: parKeyVaultOrMhsmAdminObjectId
+ }
+}
+
+resource resNetworkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-07-01' = {
+ name: parNetworkSecurityGroupName
+ location: parDeploymentLocation
+}
+
+resource resVirtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = {
+ name: parVirtualNetworkName
+ location: parDeploymentLocation
+ tags: parTags
+ properties: {
+ addressSpace: {
+ addressPrefixes: [
+ parVnetAddressPrefix
+ ]
+ }
+ subnets: [
+ {
+ name: varSubnetName
+ properties: {
+ addressPrefix: parSubnetAddressPrefix
+ networkSecurityGroup: {
+ id: resNetworkSecurityGroup.id
+ }
+ }
+ }
+ ]
+ }
+}
+
+// Peering from new VNET to the hub network in SLZ -connectivity subscription
+module modPeeringToHub '../../../common/modules/module.peering.bicep' = {
+ name: 'VentPeering.Outgoing-${parTimestamp}'
+ params: {
+ parHomeNetworkName: resVirtualNetwork.name
+ parRemoteNetworkId: parHubNetworkResourceId
+ parAllowGatewayTransit: false
+ parUseRemoteGateways: true
+ }
+}
+
+// Inverse peering from hub network in SLZ -connectivity subscription to the new VNET
+module modPeeringFromHub '../../../common/modules/module.peering.bicep' = {
+ name: 'VentPeering.Outgoing-${parTimestamp}'
+ scope: resourceGroup(varHubNetworkSubscription, varHubNetworkResourceGroup)
+ params: {
+ parHomeNetworkName: varHubNetworkName
+ parRemoteNetworkId: resVirtualNetwork.id
+ parAllowGatewayTransit: true
+ parUseRemoteGateways: false
+ }
+}
+
+output outDiskEncryptionSetId string = diskEncryptionSet.id
+output outVmkeyId string = resVmDiskEncryptionKey.id
+output outVmkeyUrl string = resVmDiskEncryptionKey.properties.keyUriWithVersion
+output outVirtualNetworkName string = resVirtualNetwork.name
+output outVirtualNetworkId string = resVirtualNetwork.id
+output outAttestationProviderName string = resAttestationProvider.name
+output outAttestationProviderAttestUri string = resAttestationProvider.properties.attestUri
diff --git a/workloadAccelerators/confidentialVirtualMachine/scripts/parameters/confidentialVirtualMachine.parameters.json b/workloadAccelerators/confidentialVirtualMachine/scripts/parameters/confidentialVirtualMachine.parameters.json
new file mode 100644
index 0000000..c056113
--- /dev/null
+++ b/workloadAccelerators/confidentialVirtualMachine/scripts/parameters/confidentialVirtualMachine.parameters.json
@@ -0,0 +1,43 @@
+{
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "parDeploymentPrefix": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "minLength": 2,
+ "maxLength": 5,
+ "value": null,
+ "description": "The prefix that will be added to all resources created by this deployment."
+ },
+ "parDeploymentLocation": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "value": null,
+ "description": "Location of the deployment."
+ },
+ "parHubNetworkResourceId": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "value": null,
+ "description": "Resource ID of the SLZ's hub vnet from the %slzprefix%-connectivity subscription."
+ },
+ "parAdminPasswordOrKey": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "value": null,
+ "description": "The password for the Admin user created on Confidential Virtual Machine."
+ },
+ "parVnetAddressPrefix": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "value": null,
+ "description": "IP address space of the VNET created for this template, in CIDR notation. Must be unique across all vnets peered against the SLZ hub vnet as referenced above."
+ },
+ "parAuthenticationType": {
+ "type": "string",
+ "usedBy": "Confidential virtual machine template",
+ "value": "password",
+ "description": "The authentication type of the VM."
+ }
+ }
+}
diff --git a/workloadAccelerators/lighthouse/README.md b/workloadAccelerators/lighthouse/README.md
new file mode 100644
index 0000000..4c50927
--- /dev/null
+++ b/workloadAccelerators/lighthouse/README.md
@@ -0,0 +1,6 @@
+# Azure Lighthouse template
+
+Follow this link to learn more about this template:
+
+[**Azure Lighthouse template**](./docs/lighthouseAccelerator.md)
+
diff --git a/workloadAccelerators/lighthouse/docs/images/LightHouseTenantID.png b/workloadAccelerators/lighthouse/docs/images/LightHouseTenantID.png
new file mode 100644
index 0000000..2d3ff0e
Binary files /dev/null and b/workloadAccelerators/lighthouse/docs/images/LightHouseTenantID.png differ
diff --git a/workloadAccelerators/lighthouse/docs/images/LighthouseCustomers.png b/workloadAccelerators/lighthouse/docs/images/LighthouseCustomers.png
new file mode 100644
index 0000000..c798a6b
Binary files /dev/null and b/workloadAccelerators/lighthouse/docs/images/LighthouseCustomers.png differ
diff --git a/workloadAccelerators/lighthouse/docs/images/LighthouseServiceProviders.png b/workloadAccelerators/lighthouse/docs/images/LighthouseServiceProviders.png
new file mode 100644
index 0000000..d8be86c
Binary files /dev/null and b/workloadAccelerators/lighthouse/docs/images/LighthouseServiceProviders.png differ
diff --git a/workloadAccelerators/lighthouse/docs/images/LighthouseSubscriptionID.png b/workloadAccelerators/lighthouse/docs/images/LighthouseSubscriptionID.png
new file mode 100644
index 0000000..5b850e9
Binary files /dev/null and b/workloadAccelerators/lighthouse/docs/images/LighthouseSubscriptionID.png differ
diff --git a/workloadAccelerators/lighthouse/docs/lighthouseAccelerator.md b/workloadAccelerators/lighthouse/docs/lighthouseAccelerator.md
new file mode 100644
index 0000000..9069fa6
--- /dev/null
+++ b/workloadAccelerators/lighthouse/docs/lighthouseAccelerator.md
@@ -0,0 +1,87 @@
+# Azure Lighthouse template
+
+Azure Lighthouse is a management service that enables service providers to manage Azure resources for multiple customers from a single control plane.
+
+The Azure Lighthouse template empowers customers to grant service providers access to their tenant for the management of Azure services like [Azure Policy](https://learn.microsoft.com/azure/lighthouse/how-to/policy-at-scale), [Microsoft Sentinel](https://learn.microsoft.com/azure/lighthouse/how-to/manage-sentinel-workspaces), [Azure Arc](https://learn.microsoft.com/azure/lighthouse/how-to/manage-hybrid-infrastructure-arc), and more. The [activity logs](https://learn.microsoft.com/azure/lighthouse/how-to/view-service-provider-activity) are stored in the customer's tenant, enabling them to monitor changes to their tenant.
+
+For more information, see [Azure Lighthouse](https://learn.microsoft.com/azure/lighthouse/overview). If you don't wish to grant tenant access to any service provider, you don't need to install this template.
+
+The template requires two prerequisites:
+* Active subscription to deploy this template
+* [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) permissions on the management group in the managed tenant under which Azure Lighthouse should manage subscriptions
+
+There's no hard dependency of [Sovereign Landing Zone (SLZ)](https://github.com/Azure/sovereign-landing-zone), however we have implemented and tested this template given an SLZ management group hierarchy and existing policies.
+
+We have created a policy to enforce Lighthouse on subscriptions under the scope of the given management group. If newer subscriptions are added after deployment, the Azure Lighthouse template will integrate those subscriptions via a logic application that triggers every four hours.
+
+## Deploy Azure Lighthouse template
+
+> [!NOTE]
+> This deployment is only supported for the \'Owner\' role now. We're working on adding roles with more granular permissions in future releases.**
+
+1. Open PowerShell 7.x. command shell.
+
+1. Clone the [cloud for sovereignty apps](https://github.com/Azure/cloud-for-sovereignty-quickstarts) repo.
+
+ ``` powershell
+ git clone https://github.com/Azure/cloud-for-sovereignty-quickstarts
+ ```
+
+3. Run commands to authenticate to Azure, where `subscription-id` is the subscription ID where the deployment occurs. We recommend running it under the `Management` subscription ID under the `Platform` management group. See [image](images/LighthouseSubscriptionID.png) to identify the `Management` subscription ID.
+
+ ``` powershell
+ Login-AzAccount
+
+ Connect-AzAccount -Subscription
+ ```
+
+1. Navigate to `cloud-for-sovereignty-quickstarts\workloadAccelerators\lighthouse\scripts\parameters\` and open ``lighthouse.parameters.json`` in a text editor, for example VS Code.
+ ``` powershell
+ cd .\cloud-for-sovereignty-quickstarts\workloadAccelerators\lighthouse\scripts\parameters\
+ ```
+
+ ``` powershell
+ code .
+ ```
+
+1. Update the parameters and save the file. Use the [Parameters](#parameters) table for
+ assistance.
+
+1. Change directory to scripts and run deployment PowerShell script.
+ ``` powershell
+ cd ..
+ .\lighthouseAccelerator.ps1
+ ```
+
+1. A successful deployment finishes with the following statements:
+ ``` powershell
+ >>> Policy Remediation Started
+ >>> Policy Remediation Successful
+ >>> Lighthouse deployment Successful
+ ```
+
+1. Confirm deployment completion on providers by navigating to the Azure portal [Service providers](https://portal.azure.com/#view/Microsoft_Azure_CustomerHub/ServiceProvidersBladeV2/~/providers) offers page in your tenant where you want to manage services for your organization.
+
+ - Search for the offer name that you specified in the parameters file ``parLighthouseOfferName``.
+ - Search also under ``Service providers - Delegations`` tab to access on-demand auditing and reporting across all service providers actions. For more information, see [image](./images/LighthouseServiceProviders.png).
+
+1. Confirm deployment completion for customers by navigating to the Azure portal [My customers - Customers](https://portal.azure.com/#view/Microsoft_Azure_CustomerHub/MyCustomersBladeV2/~/customers) page.
+ - Make sure you have the ``Show only delegation selected in the global subscriptions filter`` checked.
+ - You should find the customer's name as the tenant you deployed your Lighthouse in, and ``Delegations`` for existing subscriptions with the assigned ``parRoleDefinitionId`` role in the config parameter.
+ - From here, you can select each subscription that you want to manage and see all related resources. For more information, see [image](./images/LighthouseCustomers.png).
+
+## Parameters
+
+The Parameters table shows the descriptions and accepted values for all parameters within the ``lighthouse.parameters.json`` file. All parameters marked with ``*`` are required.
+
+| Parameter |Description | Guidance, examples |
+|---------------------|---------------|----------------------------------|
+| parDeploymentPrefix* | Deployment prefix used for creating deployment resource group and resources. If you're deploying the template on an SLZ, we recommend having this prefix match the SLZ’s prefix. | Five characters or less, lowercase, for example: mcfs |
+| parManagementGroupId* | The Id of the top level management group where we enable Lighthouse on all subscriptions under it. In case you are deploying on SLZ, it's the top level management group of the SLZ| Maximum 10 characters, for example {slz-name} |
+| parDeploymentLocation* | Location of the deployment. Location must be part of the [allowed locations list](../scripts/lighthouse.bicep) if deploying this on an SLZ deployment.| for example: northeurope |
+| parLighthouseManagementTenantId* | The tenant ID of the service provider. **Note: This value can't be the same as the tenant ID where this template is being deployed.* | In the Azure portal, sign in to the service provider tenant. Navigate to Azure AD to find the tenant ID. For more information, see [image](./images/LightHouseTenantID.png) for help with finding this information. |
+| parLighthouseOfferName* | Name of the service provider offer. | Maximum 20 characters, for example: "Lighthouse offer" |
+| parLighthouseOfferDescription* | Brief description of the offer. | for example: “Lighthouse description” |
+| parPrincipalId*| The principal ID value of the user, group, or service principal in the service provider tenant, which gives access to your tenant. | From the Azure AD, locate the ObjectId for the user, group, or service principal in the service provider's tenant. |
+| parPrincipalIdDisplayName* | Display name to identify the principal ID. | Group or user or service principal name |
+| parRoleDefinitionId* | Role to be assigned for service provider, all built-in roles are currently supported with Azure Lighthouse, but with the some exceptions. Note:Lighthouse does not support owner role. For more information on predefined roles, navigate to [Azure RBAC roles](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles) and reference [Roles for Azure Lighthouse scenarios](https://learn.microsoft.com/azure/lighthouse/concepts/tenants-users-roles#role-support-for-azure-lighthouse) for restrictions on role usage.| Contributor Role is the default value
diff --git a/workloadAccelerators/lighthouse/scripts/lighthouse.bicep b/workloadAccelerators/lighthouse/scripts/lighthouse.bicep
new file mode 100644
index 0000000..b27bcee
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/lighthouse.bicep
@@ -0,0 +1,225 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: This Bicep file deploys a Logic App that will be used to register the Lighthouse Managed Services Provider (MSP) offer in a tenant.
+ AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+@description('The tenant ID for Lighthouse.')
+param parLighthouseManagementTenantId string
+
+@description('The management group id')
+@minLength(2)
+@maxLength(10)
+param parManagementGroupId string
+
+@minLength(1)
+@maxLength(20)
+@description('The Lighthouse MSP offer name.')
+param parLighthouseOfferName string
+
+@description('The Lighthouse MSP offer description.')
+param parLighthouseOfferDescription string
+
+@description('The principal ID for authorizations.')
+param parPrincipalId string
+
+@description('The principal ID display name for authorizations.')
+param parPrincipalIdDisplayName string
+
+@description('Timestamp with format yyyyMMddTHHmmssZ. Default value set to Execution Timestamp to avoid deployment contention.')
+param parTimestamp string = utcNow()
+
+@description('Subscription where logic app will be deployed')
+param parDeploymentSubscriptionId string
+
+@description('Name of Logic App')
+param parLogicAppName string
+
+@description('Role that will be assigned to service provider ')
+param parRoleDefinitionId string
+
+@description('Name of the User Assigned Managed Identity used in some RBAC scenarios (e.g., for the Disk Encryption Set).')
+param parManagedIdentityName string = '${parManagementGroupId}-Lighthouse-id'
+
+@description('Deployment location')
+@allowed([
+ 'asia'
+ 'asiapacific'
+ 'australia'
+ 'australiacentral'
+ 'australiacentral2'
+ 'australiaeast'
+ 'australiasoutheast'
+ 'brazil'
+ 'brazilsouth'
+ 'brazilsoutheast'
+ 'canada'
+ 'canadacentral'
+ 'canadaeast'
+ 'centralindia'
+ 'centralus'
+ 'centraluseuap'
+ 'centralusstage'
+ 'eastasia'
+ 'eastasiastage'
+ 'eastus'
+ 'eastus2'
+ 'eastus2euap'
+ 'eastus2stage'
+ 'eastusstage'
+ 'eastusstg'
+ 'europe'
+ 'france'
+ 'francecentral'
+ 'francesouth'
+ 'germany'
+ 'germanynorth'
+ 'germanywestcentral'
+ 'global'
+ 'india'
+ 'japan'
+ 'japaneast'
+ 'japanwest'
+ 'jioindiacentral'
+ 'jioindiawest'
+ 'korea'
+ 'koreacentral'
+ 'koreasouth'
+ 'northcentralus'
+ 'northcentralusstage'
+ 'northeurope'
+ 'norway'
+ 'norwayeast'
+ 'norwaywest'
+ 'qatarcentral'
+ 'singapore'
+ 'southafrica'
+ 'southafricanorth'
+ 'southafricawest'
+ 'southcentralus'
+ 'southcentralusstage'
+ 'southcentralusstg'
+ 'southeastasia'
+ 'southeastasiastage'
+ 'southindia'
+ 'swedencentral'
+ 'switzerland'
+ 'switzerlandnorth'
+ 'switzerlandwest'
+ 'uae'
+ 'uaecentral'
+ 'uaenorth'
+ 'uk'
+ 'uksouth'
+ 'ukwest'
+ 'unitedstates'
+ 'unitedstateseuap'
+ 'westcentralus'
+ 'westeurope'
+ 'westindia'
+ 'westus'
+ 'westus2'
+ 'westus2stage'
+ 'westus3'
+ 'westusstage'
+])
+param parLocation string
+
+@description('Name of resource group')
+var varResourceGroupName = '${parManagementGroupId}-rg-lighthouse-${parLocation}'
+
+// module to create lighthouse resource group
+module modLighthouseResourceGroup '../../../common/resourceGroup/resourceGroup.bicep' = {
+ name: take('${parManagementGroupId}-Lighthouse-rg-${parLocation}-${parTimestamp}', 64)
+ scope: subscription(parDeploymentSubscriptionId)
+ params: {
+ parLocation: parLocation
+ parResourceGroupName: varResourceGroupName
+ parTelemetryOptOut: true
+ }
+}
+
+//create managed identity
+module modManagedIdentity '../../../common/Microsoft.ManagedIdentity/userAssignedIdentities/deploy.bicep' = {
+ name: 'LogicApp-Managed-Identity-${parTimestamp}'
+ scope: resourceGroup(parDeploymentSubscriptionId,varResourceGroupName)
+ dependsOn:[
+ modLighthouseResourceGroup
+ ]
+ params: {
+ parLocation: parLocation
+ parName: parManagedIdentityName
+ }
+}
+
+//Create Logic app in lighthouse resource group
+module modLighthouseLogicApp 'logicApp.bicep' = {
+ scope:resourceGroup(parDeploymentSubscriptionId,varResourceGroupName)
+ dependsOn:[
+ modLighthouseResourceGroup
+ ]
+ name: take('${parManagementGroupId}-LogicApp-${parLocation}-${parTimestamp}', 64)
+ params:{
+ parLogicAppName:parLogicAppName
+ parLocation:parLocation
+ parManagedIdentityId: modManagedIdentity.outputs.outResourceId
+ parManagedIdentityName: parManagedIdentityName
+ }
+}
+
+// Module - deploy logicapp role definition
+module modLogicAppRoleDefinition '../../../common/customRoles/customRoleDefinition.bicep' = {
+ scope: managementGroup(parManagementGroupId)
+ name: take('${parManagementGroupId}-LogicApp-RoleDefintion-${parLocation}-${parTimestamp}', 64)
+ params:{
+ roleName:'${parManagementGroupId} RP Register Role ${parLocation}'
+ actions:[
+ 'Microsoft.ManagedServices/register/action'
+ ]
+ roleDescription:'To register managed services resource provider'
+
+ }
+}
+
+// Module - deploy logicapp role assignment
+module modRoleAssignmentManagementGroup '../../../common/customRoles/customRoleAssignment.bicep' = {
+ name: take('${parManagementGroupId}-deploy-Lighthouse-Role-Assignment-${parTimestamp}', 64)
+ scope: managementGroup(parManagementGroupId)
+ params: {
+ parRoleDefinitionId: modLogicAppRoleDefinition.outputs.outRoleDefinitionId
+ parPrincipalId:modManagedIdentity.outputs.outPrincipalId
+ parPrincipalType: 'ServicePrincipal'
+ }
+ dependsOn:[
+ modLogicAppRoleDefinition
+ modLighthouseLogicApp
+ ]
+}
+
+// Module - create lighthouse custom policy definition and assignment
+module modLighthousePolicy 'policy.bicep' = {
+ scope: managementGroup(parManagementGroupId)
+ name: take('${parManagementGroupId}-Lighthouse-policy-${parLocation}-${parTimestamp}', 64)
+ params: {
+ parManagementGroupId:parManagementGroupId
+ parManagedByName:parLighthouseOfferName
+ parManagedByDescription:parLighthouseOfferDescription
+ parManagedByTenantId:parLighthouseManagementTenantId
+ parManagedByAuthorizations:[
+ {
+ principalId: parPrincipalId
+ principalIdDisplayName: parPrincipalIdDisplayName
+ roleDefinitionId: parRoleDefinitionId
+
+ }
+ ]
+ parLocation:parLocation
+ }
+}
+
+output outPolicyDefinitionId string = modLighthousePolicy.outputs.outPolicyDefinitionId
+output outPolicyAssignmentId string = modLighthousePolicy.outputs.outPolicyAssignmentId
+output outLogicAppIdentityId string = modManagedIdentity.outputs.outPrincipalId
+output outLighthouseResourceGroupId string = modLighthouseResourceGroup.outputs.outResourceGroupId
diff --git a/workloadAccelerators/lighthouse/scripts/lighthouseAccelerator.ps1 b/workloadAccelerators/lighthouse/scripts/lighthouseAccelerator.ps1
new file mode 100644
index 0000000..1c54369
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/lighthouseAccelerator.ps1
@@ -0,0 +1,236 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+<#
+.SYNOPSIS
+This PowerShell script serves as the overarching script to deploy the workload template either in its entirety or in a piecemeal manner the below individual modules.
+
+.DESCRIPTION
+- Executes the individual modules - lighthouse
+
+#>
+
+using namespace System.Collections
+param (
+ $parAttendedLogin = $true
+)
+
+#reference to common scripts
+. "..\..\..\common\common.ps1"
+
+#Processing parameters from JSON and creating a hash table
+$varParameters = @{}
+
+$varWorkloadParameters = Get-Content -Path '.\parameters\lighthouse.parameters.json' | ConvertFrom-Json
+$varWorkloadParameters.parameters.psobject.properties | ForEach-Object {
+ if ($_.value.Value -eq $null -or $_.value.Value.count -eq 0) {
+ $varParameters.add($_.Name, (new-Object PsObject -property @{value = $_.value.defaultValue; defaultValue = $_.value.defaultValue }))
+ }
+ else {
+ $varParameters.add($_.Name, (new-Object PsObject -property @{value = $_.value.Value; defaultValue = $_.value.defaultValue }))
+ }
+}
+
+#constants
+$varMaxRetryAttemptTransientErrorRetry = 3
+$varRetry = $true
+$varRetryWaitTimeTransientErrorRetry = 60
+
+#bicep files
+$varLighthouse = '.\lighthouse.bicep'
+$varPolicyRemediation = '.\policyRemediation.bicep'
+
+#Parameters
+$varLighthouseParams = @('parDeploymentPrefix', 'parDeploymentLocation', 'parLighthouseManagementTenantId', 'parLighthouseOfferName', 'parLighthouseOfferDescription', 'parPrincipalId', 'parPrincipalIdDisplayName', 'parRoleDefinitionId')
+
+#variables to support retry for known transient errors
+$varMaxRetryAttemptTransientErrorRetry = 3
+$varRetryWaitTimeTransientErrorRetry = 60
+
+<#
+.Description
+ Invokes Policy Remediation for existing subscriptions
+#>
+function Invoke-PolicyRemediation {
+ param($varPolicyAssignmentId)
+ #policy remediation
+ $guid = New-Guid
+ $parDeploymentLocation = $varParameters.parDeploymentLocation.value
+ $varDeploymentName = ("$guid" + $varPolicySetDefinitionName)
+ $deploymentName = $varDeploymentName.Length -ge 64 ? $varDeploymentName.Substring(0, 64) : $varDeploymentName
+ $parRemediationName = "rem-" + $deploymentName
+ $parManagementGroupId = $varParameters.parManagementGroupId.value
+ $varTotalWaitTime = 0
+ $varLoopCounter = 0
+
+ $parameters = @{
+ parPolicyRemediationName = $parRemediationName
+ parPolicyAssignmentId = $varPolicyAssignmentId
+ parPolicyDefinitionReferenceId = ''
+ }
+
+ while ($varTotalWaitTime -lt $varMaxRetryAttemptTransientErrorRetry) {
+ try {
+ Write-Information ">>> Policy Remediation Started" -InformationAction Continue
+ $modLighthousePolicyRemediation = New-AzManagementGroupDeployment `
+ -Name $varDeploymentName `
+ -Location $parDeploymentLocation `
+ -TemplateFile $varPolicyRemediation `
+ -ManagementGroupId $parManagementGroupId `
+ -TemplateParameterObject $parameters `
+ -WarningAction Ignore
+
+ if (!$modLighthousePolicyRemediation -or $modLighthousePolicyRemediation.ProvisioningState -eq "Failed") {
+ Write-Error "Error while executing policy remediation deployment" -ErrorAction Stop
+ }
+ else {
+ Write-Information ">>> Policy Remediation Successful" -InformationAction Continue
+ }
+
+ return
+ }
+ catch {
+ $varLoopCounter++
+ $varException = $_.Exception
+ $varErrorDetails = $_.ErrorDetails
+ $varTrace = $_.ScriptStackTrace
+ Write-Error "$varException \n $varErrorDetails \n $varTrace" -ErrorAction Continue
+
+ if ($varRetry -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) {
+ Write-Information ">>> Retrying deployment after waiting for $varRetryWaitTimeTransientErrorRetry secs" -InformationAction Continue
+ Start-Sleep -Seconds $varRetryWaitTimeTransientErrorRetry
+ }
+ else {
+ Write-Error ">>> Error occurred in policy remediation deployment. Please try after addressing the error : $varException \n $varErrorDetails \n $varTrace" -ErrorAction Stop
+ }
+ }
+ }
+}
+
+<#
+.Description
+ Invokes Policy evaluation for the lighthouse policy
+#>
+function Invoke-PolicyEvaluation {
+ param($varSubscriptions)
+ $varSubscriptionsCount = $varSubscriptions.count
+ if ($varSubscriptionsCount -eq 0) {
+ Write-Information ">>> No subscriptions to evaluate policies" -ErrorAction Stop
+ }
+
+ Write-Information ">>> Policy scan will be executed in synchronous mode. The process may take up to an hour." -InformationAction Continue
+ $varCounter = 1
+ foreach ($subscription in $varSubscriptions) {
+ # register for ManagedServices and PolicyInsights RP
+ $subscriptionId = $subscription.subscriptionId;
+ $subscriptionAvailable = Get-AzSubscription -SubscriptionId $subscriptionId
+ if ($subscriptionAvailable -eq "[]") {
+ Write-Error ">>> Please refresh credentials by running following commands: Disconnect-AzAccount and Connect-AzAccount and retry the deployment" -ErrorAction Stop
+ }
+
+ # This is not logic requirement, but have to register Microsoft.Network early to avoid Subscription XXXXX-XXXXX-XXXXXXX-XXXXXXX is not registered with NRP because of registration delay.
+ Write-Information ">>> Registering Microsoft.Network resource provider for the existing subscription $subscriptionId ..." -InformationAction Continue
+ Set-AzContext -Subscription $subscriptionId
+ Register-ResourceProvider "Microsoft.Network"
+
+ Write-Information ">>> Registering Managed.Services resource provider for the existing subscription $subscriptionId ..." -InformationAction Continue
+ Register-ResourceProvider 'Microsoft.ManagedServices' `
+
+ Write-Information ">>> Registering Managed.PolicyInsights resource provider for the existing subscription $subscriptionId ..." -InformationAction Continue
+ Register-ResourceProvider 'Microsoft.PolicyInsights' `
+
+ #run trigger scan for policy evaluation
+ Write-Information "Executing policy evaluation scan for subscription id: $subscriptionId. Processing $varCounter subscription out of $varSubscriptionsCount " -InformationAction Continue
+ $varCounter++
+
+ Start-AzPolicyComplianceScan
+ }
+ return
+}
+
+<#
+.DESCRIPTION
+ Deploys lighthouse
+#>
+function New-Lighthouse {
+ param()
+ $parDeploymentPrefix = $varParameters.parDeploymentPrefix.value
+ $varLighthouseDeploymentName = "$parDeploymentPrefix-deploy-Lighthouse-$vartimeStamp"
+ $parManagementGroupId = $varParameters.parManagementGroupId.value
+ $varLogicAppName = "$parManagementGroupId-logicapp-$parDeploymentLocation"
+
+ $parameters = @{
+ parManagementGroupId = $parManagementGroupId
+ parLocation = $varParameters.parDeploymentLocation.value
+ parLogicAppName = $varLogicAppName
+ parDeploymentSubscriptionId = $varDeploymentSubscriptionId
+ parLighthouseManagementTenantId = $varParameters.parLighthouseManagementTenantId.value
+ parLighthouseOfferName = $varParameters.parLighthouseOfferName.value
+ parLighthouseOfferDescription = $varParameters.parLighthouseOfferDescription.value
+ parPrincipalId = $varParameters.parPrincipalId.value
+ parPrincipalIdDisplayName = $varParameters.parPrincipalIdDisplayName.value
+ parRoleDefinitionId = $varParameters.parRoleDefinitionId.value
+ }
+
+ while ($varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) {
+ try {
+ Write-Information ">>> Lighthouse deployment started" -InformationAction Continue
+ $modLighthouse = New-AzManagementGroupDeployment `
+ -Name $varLighthouseDeploymentName `
+ -Location $parDeploymentLocation `
+ -ManagementGroupId $parManagementGroupId `
+ -TemplateFile $varLighthouse `
+ -TemplateParameterObject $parameters `
+ -WarningAction Ignore
+
+ if (!$modLighthouse -or $modLighthouse.ProvisioningState -eq "Failed") {
+ Write-Error "Error while executing lighthouse deployment script" -ErrorAction Stop
+ }
+
+ return $modLighthouse
+ }
+ catch {
+ $varLoopCounter++
+ $varException = $_.Exception
+ $varErrorDetails = $_.ErrorDetails
+ $varTrace = $_.ScriptStackTrace
+ Write-Error "$varException \n $varErrorDetails \n $varTrace" -ErrorAction Continue
+
+ if ($varRetry -and $varLoopCounter -lt $varMaxRetryAttemptTransientErrorRetry) {
+ Write-Information ">>> Retrying deployment after waiting for $varRetryWaitTimeTransientErrorRetry secs" -InformationAction Continue
+ Start-Sleep -Seconds $varRetryWaitTimeTransientErrorRetry
+ }
+ else {
+ Write-Error ">>> Error occurred in Lighthouse deployment. Please try after addressing the error : $varException \n $varErrorDetails \n $varTrace" -ErrorAction Stop
+ }
+ }
+ }
+}
+
+Confirm-Parameters($varLighthouseParams)
+
+if ($parAttendedLogin) {
+ # Confirm Prerequisites
+ Confirm-Prerequisites -parConfirmAZResourceGraphVersion 1
+}
+
+#Fetch all existing subscriptions
+$parDeploymentPrefix = $varParameters.parDeploymentPrefix.value
+$parManagementGroupId = $varParameters.parManagementGroupId.value
+$parDeploymentLocation = $varParameters.parDeploymentLocation.value
+$varDeploymentSubscriptionId = (Get-AzContext).Subscription.id
+$varSubscriptions = Search-AzGraph -Query "ResourceContainers | where type =~ 'microsoft.resources/subscriptions'" -ManagementGroup $parManagementGroupId
+$varSubscriptions = $varSubscriptions | Where-Object { $_.properties.state -eq "Enabled" }
+$varIsMgSubscription = $varSubscriptions | Where-Object { $_.subscriptionId -eq $varDeploymentSubscriptionId }
+
+if ($null -eq $varIsMgSubscription) {
+ Write-Error "The subscription $varDeploymentSubscriptionId does not under the management group $parManagementGroupId or disabled. Please use the subscription which is created by Sovereign Landing Zone." -ErrorAction Stop
+}
+
+$modLighthouseOutputs = New-Lighthouse
+$varPolicyAssignmentId = $modLighthouseOutputs.Outputs.outPolicyAssignmentId.value
+
+#invoke policy evaluation
+Invoke-PolicyEvaluation $varSubscriptions
+#invoke policy remediation
+Invoke-PolicyRemediation $varPolicyAssignmentId
+Write-Information ">>> Lighthouse deployment Successful" -InformationAction Continue
diff --git a/workloadAccelerators/lighthouse/scripts/logicApp.bicep b/workloadAccelerators/lighthouse/scripts/logicApp.bicep
new file mode 100644
index 0000000..9658470
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/logicApp.bicep
@@ -0,0 +1,232 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: Logic App to register a resource provider in a subscription
+ AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'resourceGroup'
+
+@description('Name of logic app')
+param parLogicAppName string
+
+@allowed([
+ 'asia'
+ 'asiapacific'
+ 'australia'
+ 'australiacentral'
+ 'australiacentral2'
+ 'australiaeast'
+ 'australiasoutheast'
+ 'brazil'
+ 'brazilsouth'
+ 'brazilsoutheast'
+ 'canada'
+ 'canadacentral'
+ 'canadaeast'
+ 'centralindia'
+ 'centralus'
+ 'centraluseuap'
+ 'centralusstage'
+ 'eastasia'
+ 'eastasiastage'
+ 'eastus'
+ 'eastus2'
+ 'eastus2euap'
+ 'eastus2stage'
+ 'eastusstage'
+ 'eastusstg'
+ 'europe'
+ 'france'
+ 'francecentral'
+ 'francesouth'
+ 'germany'
+ 'germanynorth'
+ 'germanywestcentral'
+ 'global'
+ 'india'
+ 'japan'
+ 'japaneast'
+ 'japanwest'
+ 'jioindiacentral'
+ 'jioindiawest'
+ 'korea'
+ 'koreacentral'
+ 'koreasouth'
+ 'northcentralus'
+ 'northcentralusstage'
+ 'northeurope'
+ 'norway'
+ 'norwayeast'
+ 'norwaywest'
+ 'qatarcentral'
+ 'singapore'
+ 'southafrica'
+ 'southafricanorth'
+ 'southafricawest'
+ 'southcentralus'
+ 'southcentralusstage'
+ 'southcentralusstg'
+ 'southeastasia'
+ 'southeastasiastage'
+ 'southindia'
+ 'swedencentral'
+ 'switzerland'
+ 'switzerlandnorth'
+ 'switzerlandwest'
+ 'uae'
+ 'uaecentral'
+ 'uaenorth'
+ 'uk'
+ 'uksouth'
+ 'ukwest'
+ 'unitedstates'
+ 'unitedstateseuap'
+ 'westcentralus'
+ 'westeurope'
+ 'westindia'
+ 'westus'
+ 'westus2'
+ 'westus2stage'
+ 'westus3'
+ 'westusstage'
+])
+@description('Location for the logic app and its connectors')
+param parLocation string
+
+@allowed([
+ 'Month'
+ 'Week'
+ 'Day'
+ 'Hour'
+ 'Minute'
+ 'Second'
+])
+@description('frequency of recurrence. Default:Hour')
+param parFrequency string = 'Hour'
+
+@description('interval of recurrence. default:4')
+param parInterval int = 4
+
+@description('User assigned managed identity')
+param parManagedIdentityId string
+
+@description('User assigned managed identity name')
+param parManagedIdentityName string
+
+resource resLogicapp 'Microsoft.Logic/workflows@2019-05-01' = {
+ name: parLogicAppName
+ location: parLocation
+ identity: {
+ type: 'UserAssigned'
+ userAssignedIdentities:{
+ '${parManagedIdentityId}':{
+ }
+ }
+ }
+ properties: {
+ state: 'Enabled'
+ definition: {
+ '$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
+ contentVersion: '1.0.0.0'
+ parameters: {
+ '$connections': {
+ defaultValue: {
+ }
+ type: 'Object'
+ }
+ }
+ triggers: {
+ Recurrence: {
+ recurrence: {
+ frequency: parFrequency
+ interval: parInterval
+ }
+ type: 'Recurrence'
+ }
+ }
+ actions: {
+ List_subscriptions: {
+ runAfter: {
+ }
+ type: 'ApiConnection'
+ inputs: {
+ host: {
+ connection: {
+ name: '@parameters(\'$connections\')[\'GetSubscriptions\'][\'connectionId\']'
+ }
+ }
+ method: 'get'
+ path: '/subscriptions'
+ queries: {
+ 'x-ms-api-version': '2016-06-01'
+ }
+ }
+ }
+ Subscription_Loop: {
+ foreach: '@body(\'List_subscriptions\')?[\'value\']'
+ actions: {
+ Register_resource_provider: {
+ runAfter: {
+ }
+ type: 'ApiConnection'
+ inputs: {
+ host: {
+ connection: {
+ name: '@parameters(\'$connections\')[\'GetSubscriptions\'][\'connectionId\']'
+ }
+ }
+ method: 'post'
+ path: '/subscriptions/@{encodeURIComponent(items(\'Subscription_Loop\')?[\'subscriptionId\'])}/providers/@{encodeURIComponent(\'Microsoft.ManagedServices\')}/register'
+ queries: {
+ 'x-ms-api-version': '2016-06-01'
+ }
+ }
+ }
+ }
+ runAfter: {
+ List_subscriptions: [
+ 'Succeeded'
+ ]
+ }
+ type: 'Foreach'
+ }
+ }
+ outputs: {
+ }
+ }
+ parameters: {
+ '$connections': {
+ value: {
+ GetSubscriptions: {
+ connectionId: '${subscription().id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.Web/connections/GetSubscriptions'
+ connectionName: 'GetSubscriptions'
+ connectionProperties: {
+ authentication: {
+ identity: '${subscription().id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${parManagedIdentityName}'
+ type: 'ManagedServiceIdentity'
+ }
+ }
+ id: '${subscription().id}/providers/Microsoft.Web/locations/${parLocation}/managedApis/arm'
+ }
+ }
+ }
+ }
+ }
+ dependsOn: [
+ resGetSubscription
+ ]
+}
+
+resource resGetSubscription 'Microsoft.Web/connections@2016-06-01' = {
+ name: 'GetSubscriptions'
+ location: parLocation
+ properties: {
+ displayName: '${parLogicAppName}-connection'
+ customParameterValues: {
+ }
+ api: {
+ id:subscriptionResourceId('Microsoft.Web/locations/managedApis',parLocation,'arm')
+ }
+ parameterValueType: 'Alternative'
+ }
+}
diff --git a/workloadAccelerators/lighthouse/scripts/parameters/lighthouse.parameters.json b/workloadAccelerators/lighthouse/scripts/parameters/lighthouse.parameters.json
new file mode 100644
index 0000000..3fb7ce5
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/parameters/lighthouse.parameters.json
@@ -0,0 +1,155 @@
+{
+ "contentVersion": "1.0.0.0",
+ "description": "",
+ "parameters": {
+ "parDeploymentPrefix": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "minLength": 2,
+ "maxLength": 5,
+ "defaultValue": "mcfs",
+ "value": null,
+ "description": "The prefix that will be added to all resources created by this deployment."
+ },
+ "parManagementGroupId": {
+ "type": "string",
+ "usedBy": "all and bootstrap",
+ "maxLength": 10,
+ "defaultValue": "",
+ "value": null,
+ "description": "The top level management group to enable service providers access to downstream subscriptions."
+ },
+ "parDeploymentLocation": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": null,
+ "value": null,
+ "allowedValues": [
+ "asia",
+ "asiapacific",
+ "australia",
+ "australiacentral",
+ "australiacentral2",
+ "australiaeast",
+ "australiasoutheast",
+ "brazil",
+ "brazilsouth",
+ "brazilsoutheast",
+ "canada",
+ "canadacentral",
+ "canadaeast",
+ "centralindia",
+ "centralus",
+ "centraluseuap",
+ "centralusstage",
+ "eastasia",
+ "eastasiastage",
+ "eastus",
+ "eastus2",
+ "eastus2euap",
+ "eastus2stage",
+ "eastusstage",
+ "eastusstg",
+ "europe",
+ "france",
+ "francecentral",
+ "francesouth",
+ "germany",
+ "germanynorth",
+ "germanywestcentral",
+ "global",
+ "india",
+ "japan",
+ "japaneast",
+ "japanwest",
+ "jioindiacentral",
+ "jioindiawest",
+ "korea",
+ "koreacentral",
+ "koreasouth",
+ "northcentralus",
+ "northcentralusstage",
+ "northeurope",
+ "norway",
+ "norwayeast",
+ "norwaywest",
+ "qatarcentral",
+ "singapore",
+ "southafrica",
+ "southafricanorth",
+ "southafricawest",
+ "southcentralus",
+ "southcentralusstage",
+ "southcentralusstg",
+ "southeastasia",
+ "southeastasiastage",
+ "southindia",
+ "swedencentral",
+ "switzerland",
+ "switzerlandnorth",
+ "switzerlandwest",
+ "uae",
+ "uaecentral",
+ "uaenorth",
+ "uk",
+ "uksouth",
+ "ukwest",
+ "unitedstates",
+ "unitedstateseuap",
+ "westcentralus",
+ "westeurope",
+ "westindia",
+ "westus",
+ "westus2",
+ "westus2stage",
+ "westus3",
+ "westusstage"
+ ],
+ "description": "Deployment location."
+ },
+ "parLighthouseManagementTenantId": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": null,
+ "value": null,
+ "description": "Tenant Id of service provider. This cannot be the same as the tenant Id where the integration is being deployed."
+ },
+ "parLighthouseOfferName": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "minLength": 1,
+ "maxLength": 20,
+ "defaultValue": null,
+ "value": null,
+ "description": "Name of Service Provider"
+ },
+ "parLighthouseOfferDescription": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": null,
+ "value": "Lighthouse offer",
+ "description": "Brief description of offer"
+ },
+ "parPrincipalId": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": null,
+ "value": null,
+ "description": "The principalId values for the users/groups/SPNs from your tenant"
+ },
+ "parPrincipalIdDisplayName": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": null,
+ "value": "Customer Contributor Group",
+ "description": "Principal Id display name"
+ },
+ "parRoleDefinitionId": {
+ "type": "string",
+ "usedBy": "lighthouse",
+ "defaultValue": "b24988ac-6180-42a0-ab88-20f7382dd24c",
+ "value": null,
+ "description": "Role to be assigned for service provider, all built-in roles are currently supported with Azure Lighthouse, but with the some exceptions. Note:Lighthouse does not support owner role."
+ }
+ }
+}
diff --git a/workloadAccelerators/lighthouse/scripts/policy.bicep b/workloadAccelerators/lighthouse/scripts/policy.bicep
new file mode 100644
index 0000000..560e46e
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/policy.bicep
@@ -0,0 +1,309 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: This is a file that deploys a policy definition and assignment to enforce Lighthouse on subscriptions.
+ AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+@description('The management group id')
+@minLength(2)
+@maxLength(10)
+param parManagementGroupId string
+
+@description('Add the tenant id provided by the MSP')
+param parManagedByTenantId string
+
+@minLength(1)
+@maxLength(20)
+@description('Add the tenant name of the provided MSP')
+param parManagedByName string
+
+@description('Add the description of the offer provided by the MSP')
+param parManagedByDescription string
+
+@description('Add the authZ array provided by the MSP')
+param parManagedByAuthorizations array
+
+@allowed([
+ 'asia'
+ 'asiapacific'
+ 'australia'
+ 'australiacentral'
+ 'australiacentral2'
+ 'australiaeast'
+ 'australiasoutheast'
+ 'brazil'
+ 'brazilsouth'
+ 'brazilsoutheast'
+ 'canada'
+ 'canadacentral'
+ 'canadaeast'
+ 'centralindia'
+ 'centralus'
+ 'centraluseuap'
+ 'centralusstage'
+ 'eastasia'
+ 'eastasiastage'
+ 'eastus'
+ 'eastus2'
+ 'eastus2euap'
+ 'eastus2stage'
+ 'eastusstage'
+ 'eastusstg'
+ 'europe'
+ 'france'
+ 'francecentral'
+ 'francesouth'
+ 'germany'
+ 'germanynorth'
+ 'germanywestcentral'
+ 'global'
+ 'india'
+ 'japan'
+ 'japaneast'
+ 'japanwest'
+ 'jioindiacentral'
+ 'jioindiawest'
+ 'korea'
+ 'koreacentral'
+ 'koreasouth'
+ 'northcentralus'
+ 'northcentralusstage'
+ 'northeurope'
+ 'norway'
+ 'norwayeast'
+ 'norwaywest'
+ 'qatarcentral'
+ 'singapore'
+ 'southafrica'
+ 'southafricanorth'
+ 'southafricawest'
+ 'southcentralus'
+ 'southcentralusstage'
+ 'southcentralusstg'
+ 'southeastasia'
+ 'southeastasiastage'
+ 'southindia'
+ 'swedencentral'
+ 'switzerland'
+ 'switzerlandnorth'
+ 'switzerlandwest'
+ 'uae'
+ 'uaecentral'
+ 'uaenorth'
+ 'uk'
+ 'uksouth'
+ 'ukwest'
+ 'unitedstates'
+ 'unitedstateseuap'
+ 'westcentralus'
+ 'westeurope'
+ 'westindia'
+ 'westus'
+ 'westus2'
+ 'westus2stage'
+ 'westus3'
+ 'westusstage'
+])
+@description('Location')
+param parLocation string
+
+@allowed([
+ 'None'
+ 'SystemAssigned'
+ 'UserAssigned'
+])
+@description('Policy Identity. Default:SystemAssigned')
+param parPolicyIdentity string = 'SystemAssigned'
+
+@description('Lighthouse Policy Assignment Name')
+param policyAssignmentName string = take('${parManagedByName} LH', 24)
+
+var varUniquePolicyDefintiion = uniqueString(parManagedByAuthorizations[0].principalId,parManagedByTenantId)
+var varPolicyDefinitionName = take('${parManagedByName}-${varUniquePolicyDefintiion}',64)
+
+@allowed([
+ 'Group'
+ 'ServicePrincipal'
+])
+@description('Principal Type. Default:ServicePrincipal')
+param parPrinicpalType string = 'ServicePrincipal'
+
+@description('Timestamp with format yyyyMMddTHHmmssZ. Default value set to Execution Timestamp to avoid deployment contention.')
+param parTimestamp string = utcNow()
+
+@description('Role Definition Ids')
+var varRBACRoleDefinitionIDs = {
+ owner: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
+ contributor: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
+ networkContributor: '4d97b98b-1d4f-4787-a291-c67834d212e7'
+ aksContributor: 'ed7f3fbd-7b88-4dd4-9017-9adb7ce333f8'
+}
+
+resource resPolicyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
+ name: varPolicyDefinitionName
+ properties: {
+ description: 'Policy to enforce Lighthouse on subscriptions'
+ displayName: 'Enforce Lighthouse for ${parManagedByName}'
+ mode: 'All'
+ policyType: 'Custom'
+ parameters: {
+ managedByTenantId: {
+ type: 'string'
+ defaultValue: parManagedByTenantId
+ metadata: {
+ description: 'Add the tenant id provided by the MSP'
+ }
+ }
+ managedByName: {
+ type: 'string'
+ defaultValue: parManagedByName
+ metadata: {
+ description: 'Add the tenant name of the provided MSP'
+ }
+ }
+ managedByDescription: {
+ type: 'string'
+ defaultValue: parManagedByDescription
+ metadata: {
+ description: 'Add the description of the offer provided by the MSP'
+ }
+ }
+ managedByAuthorizations: {
+ type: 'array'
+ defaultValue: parManagedByAuthorizations
+ metadata: {
+ description: 'Add the authZ array provided by the MSP'
+ }
+ }
+ }
+ policyRule: {
+ if: {
+ allOf: [
+ {
+ field: 'type'
+ equals: 'Microsoft.Resources/subscriptions'
+ }
+ ]
+ }
+ then: {
+ effect: 'deployIfNotExists'
+ details: {
+ type: 'Microsoft.ManagedServices/registrationDefinitions'
+ deploymentScope: 'Subscription'
+ existenceScope: 'Subscription'
+ roleDefinitionIds: [
+ '/providers/Microsoft.Authorization/roleDefinitions/${varRBACRoleDefinitionIDs.owner}'
+ ]
+ existenceCondition: {
+ allOf: [
+ {
+ field: 'type'
+ equals: 'Microsoft.ManagedServices/registrationDefinitions'
+ }
+ {
+ field: 'Microsoft.ManagedServices/registrationDefinitions/managedByTenantId'
+ equals: '[parameters(\'managedByTenantId\')]'
+ }
+ ]
+ }
+ deployment: {
+ location: parLocation
+ properties: {
+ mode: 'incremental'
+ parameters: {
+ managedByTenantId: {
+ value: '[parameters(\'managedByTenantId\')]'
+ }
+ managedByName: {
+ value: '[parameters(\'managedByName\')]'
+ }
+ managedByDescription: {
+ value: '[parameters(\'managedByDescription\')]'
+ }
+ managedByAuthorizations: {
+ value: '[parameters(\'managedByAuthorizations\')]'
+ }
+ }
+ template: {
+ '$schema': 'https://schema.management.azure.com/2018-05-01/subscriptionDeploymentTemplate.json#'
+ contentVersion: '1.0.0.0'
+ parameters: {
+ managedByTenantId: {
+ type: 'string'
+ }
+ managedByName: {
+ type: 'string'
+ }
+ managedByDescription: {
+ type: 'string'
+ }
+ managedByAuthorizations: {
+ type: 'array'
+ }
+ }
+ variables: {
+ managedByRegistrationName: '[guid(parameters(\'managedByName\'))]'
+ managedByAssignmentName: '[guid(parameters(\'managedByName\'))]'
+ }
+ resources: [
+ {
+ type: 'Microsoft.ManagedServices/registrationDefinitions'
+ apiVersion: '2019-06-01'
+ name: '[variables(\'managedByRegistrationName\')]'
+ properties: {
+ registrationDefinitionName: '[parameters(\'managedByName\')]'
+ description: '[parameters(\'managedByDescription\')]'
+ managedByTenantId: '[parameters(\'managedByTenantId\')]'
+ authorizations: '[parameters(\'managedByAuthorizations\')]'
+ }
+ }
+ {
+ type: 'Microsoft.ManagedServices/registrationAssignments'
+ apiVersion: '2019-06-01'
+ name: '[variables(\'managedByAssignmentName\')]'
+ dependsOn: [
+ '[resourceId(\'Microsoft.ManagedServices/registrationDefinitions/\', variables(\'managedByRegistrationName\'))]'
+ ]
+ properties: {
+ registrationDefinitionId: '[resourceId(\'Microsoft.ManagedServices/registrationDefinitions/\',variables(\'managedByRegistrationName\'))]'
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+//Policy Assignment
+resource resPolicyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
+ name: policyAssignmentName
+ properties: {
+ policyDefinitionId: resPolicyDefinition.id
+ displayName:'Enforce Lighthouse for ${parManagedByName}'
+ }
+ identity: {
+ type: parPolicyIdentity
+ }
+ location:parLocation
+}
+
+module modRoleAssignment '../../../common/roleAssignments/roleAssignmentManagementGroup.bicep' = {
+ name: take('${parManagementGroupId}-deploy-Role-Assignment-${parTimestamp}', 64)
+ scope: managementGroup(parManagementGroupId)
+ params: {
+ parAssigneeObjectId: resPolicyAssignment.identity.principalId
+ parAssigneePrincipalType: parPrinicpalType
+ parRoleDefinitionId: varRBACRoleDefinitionIDs.owner
+ parTelemetryOptOut: true
+ }
+}
+
+output outPolicyAssignmentId string = resPolicyAssignment.id
+output outPolicyDefinitionId string = resPolicyDefinition.id
diff --git a/workloadAccelerators/lighthouse/scripts/policyRemediation.bicep b/workloadAccelerators/lighthouse/scripts/policyRemediation.bicep
new file mode 100644
index 0000000..c0d7aff
--- /dev/null
+++ b/workloadAccelerators/lighthouse/scripts/policyRemediation.bicep
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+/*
+ SUMMARY: This file will deploy a policy remediation to a management group.
+ AUTHOR/S: Cloud for Sovereignty
+*/
+targetScope = 'managementGroup'
+
+@description('Exemption Name')
+param parPolicyRemediationName string
+
+@description('Policy Set Assignment id')
+param parPolicyAssignmentId string
+
+@description('Reference ids of Policy to be remediated')
+param parPolicyDefinitionReferenceId string
+
+@allowed([
+ 'ExistingNonCompliant'
+ 'ReEvaluateCompliance'
+])
+@description('Remediation Discovery Mode - ExistingNonCompliant')
+param parResourceDiscoveryMode string = 'ExistingNonCompliant'
+
+// Deploy the policy remediation
+resource resPolicySetRemediation 'Microsoft.PolicyInsights/remediations@2021-10-01' = if (empty(parPolicyDefinitionReferenceId) == false) {
+ name: take('${parPolicyRemediationName}-${parPolicyDefinitionReferenceId}', 64)
+ properties: {
+ policyAssignmentId: parPolicyAssignmentId
+ policyDefinitionReferenceId: parPolicyDefinitionReferenceId
+ resourceDiscoveryMode: parResourceDiscoveryMode
+ }
+}
+
+resource resPolicyRemediation 'Microsoft.PolicyInsights/remediations@2021-10-01' = if (empty(parPolicyDefinitionReferenceId)) {
+ name: parPolicyRemediationName
+ properties: {
+ policyAssignmentId: parPolicyAssignmentId
+ resourceDiscoveryMode: parResourceDiscoveryMode
+ }
+}