Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service bus managed identity integration #253

Merged
merged 8 commits into from
Sep 6, 2024
Merged
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> :mega: **Got feedback?** Fill out [this survey](https://aka.ms/eap/mwa/dotnet/survey) to help us shape the future of Enterprise App Patterns and understand whether we're focusing on the business goals and features important to you. [Microsoft Privacy Statement](https://go.microsoft.com/fwlink/?LinkId=521839)

The reference implementation provides a production-grade web application that uses best practices from our guidance and gives developers concrete examples to build their own Modern Web Application in Azure. This repository specifically demonstrates a concert ticketing application for the fictional company Relecloud, embodying the modern web app pattern with a focus on .NET technologies. It guides developers through a simulated migration from an on-premises ASP.NET application to Azure, detailing the architectural changes and enhancements that capitalize on the cloud's strengths during the initial adoption phase.
The reference implementation provides a production-grade web application that uses best practices from our guidance and gives developers concrete examples to build their own Modern Web Application in Azure. This repository specifically demonstrates a concert ticketing application for the fictional company Relecloud, embodying the modern web app pattern with a focus on .NET technologies. It guides developers through a simulated migration from an on-premises ASP.NET application to Azure, detailing the architectural changes and enhancements that capitalize on the cloud's strengths during the initial adoption phase.

This project has [a companion article in the Azure Architecture Center](https://aka.ms/eap/mwa/dotnet/doc) that describes design patterns and best practices <!--and [a six-part video series (YouTube)](https://aka.ms/eap/mwa/dotnet/videos) that details the modern web app pattern for .NET web app -->. Here's an outline of the contents in this readme:

Expand Down Expand Up @@ -32,7 +32,7 @@ This diagram describes the production deployment which is described in the [prod
## Workflow

This description details the workflow for Relecloud's concert ticketing application. It highlights key components and functionality to help you emulate its design:

- Global traffic routing: Azure Front Door acts as a global traffic manager, routing users to the primary region for optimal performance and failing over to a secondary region during outages for uninterrupted service.
- Security inspection: Incoming traffic is inspected by Azure Web Application Firewall to protect against web vulnerabilities before reaching the web app.
- Static and dynamic content delivery: Users receive static content, like the home page, immediately upon request. Dynamic content, such as 'Upcoming Concerts', is generated by making API calls to the backend, which fetches data from Azure SQL Database and returns it in a JSON format.
Expand All @@ -57,7 +57,7 @@ The following detailed deployment steps assume you are using a Dev Container ins

### 1. Clone the repo

> For Windows users, we recommend using Windows Subsystem for Linux (WSL) to [improve Dev Container performance](https://code.visualstudio.com/remote/advancedcontainers/improve-performance).
> For Windows users, we recommend using Windows Subsystem for Linux (WSL) to [improve Dev Container performance and reliability](https://code.visualstudio.com/remote/advancedcontainers/improve-performance).

```pwsh
wsl
Expand Down Expand Up @@ -194,8 +194,8 @@ azd down --purge --force

## 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
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).
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.
Expand Down
4 changes: 2 additions & 2 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ hooks:
posix:
interactive: true
shell: sh
run: ./infra/scripts/predeploy/call-configure-aca-secrets.sh && ./infra/scripts/predeploy/call-set-app-configuration.sh
run: ./infra/scripts/predeploy/call-set-app-configuration.sh
windows:
interactive: true
shell: pwsh
run: ./infra/scripts/predeploy/call-configure-aca-secrets.ps1 &&./infra/scripts/predeploy/call-set-app-configuration.ps1
run: ./infra/scripts/predeploy/call-set-app-configuration.ps1
postdeploy:
posix:
interactive: true
Expand Down
2 changes: 0 additions & 2 deletions infra/core/containers/container-registry-replication.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ targetScope = 'resourceGroup'
** Creates an Azure Container Registry resource, including permission grants and diagnostics.
*/



// ========================================================================
// PARAMETERS
// ========================================================================
Expand Down
34 changes: 2 additions & 32 deletions infra/core/containers/container-registry.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,13 @@ targetScope = 'resourceGroup'
** Creates an Azure Container Registry resource, including permission grants and diagnostics.
*/

import { DiagnosticSettings } from '../../types/DiagnosticSettings.bicep'
import { PrivateEndpointSettings } from '../../types/PrivateEndpointSettings.bicep'

// ========================================================================
// USER-DEFINED TYPES
// ========================================================================

// From: infra/types/DiagnosticSettings.bicep
@description('The diagnostic settings for a resource')
type DiagnosticSettings = {
@description('The number of days to retain log data.')
logRetentionInDays: int

@description('The number of days to retain metric data.')
metricRetentionInDays: int

@description('If true, enable diagnostic logging.')
enableLogs: bool

@description('If true, enable metrics logging.')
enableMetrics: bool
}

// From: infra/types/PrivateEndpointSettings.bicep
@description('Type describing the private endpoint settings.')
type PrivateEndpointSettings = {
@description('The name of the resource group to hold the Private DNS Zone. By default, this uses the same resource group as the resource.')
dnsResourceGroupName: string

@description('The name of the private endpoint resource.')
name: string

@description('The name of the resource group to hold the private endpoint.')
resourceGroupName: string

@description('The ID of the subnet to link the private endpoint to.')
subnetId: string
}

// From: https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/container-registry/registry/main.bicep
type roleAssignmentType = {
@description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.')
Expand Down
172 changes: 172 additions & 0 deletions infra/core/hosting/container-app.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
targetScope = 'resourceGroup'
mjrousos marked this conversation as resolved.
Show resolved Hide resolved

/*
** A Container App running on a pre-existing Managed Environment (ACA Environment)
** Copyright (C) 2024 Microsoft, Inc.
** All Rights Reserved
**
***************************************************************************
*/

// This simple ACA module is used until an updated Azure Verified Module
// is available using the new Container Apps API (version 2024-02-02-preview or newer).

// ========================================================================
// USER-DEFINED TYPES
// ========================================================================

// https://learn.microsoft.com/rest/api/containerapps/container-apps/get?view=rest-containerapps-2024-03-01&tabs=HTTP#container
type container = {
@description('Container start command arguments.')
args: string[]?

@description('Container start command.')
command: string[]?

@description('Container environment variables.')
env: environmentVar[]?

@description('Container image tag.')
image: string

@description('Custom container name.')
name: string?

@description('List of health probes for the container.')
probes: object[]?

@description('Container resource requirements.')
resources: object

@description('Container volume mounts.')
volumeMounts: volumeMount[]?
}

// https://learn.microsoft.com/rest/api/containerapps/container-apps/get?view=rest-containerapps-2024-03-01&tabs=HTTP#environmentvar
type environmentVar = {
@description('Environment variable name.')
name: string

@description('Name of the Container App secret from which to pull the environment variable value.')
secretRef: string?

@description('Non-secret environment variable value.')
value: string?
}

// https://learn.microsoft.com/rest/api/containerapps/container-apps/get?view=rest-containerapps-2024-03-01&tabs=HTTP#volumemount
type volumeMount = {
@description('Path within the container where the volume should be mounted. Must not contain \':\'.')
mountPath: string

@description('Path within the volume from which the container\'s volume should be mounted. Defaults to "" (volume\'s root).')
subPath: string?

@description('This must match the Name of a Volume.')
volumeName: string
}

// ========================================================================
// PARAMETERS
// ========================================================================

@description('The Azure region for the resource.')
param location string

@description('The name of the primary resource')
param name string

@description('The tags to associate with this resource.')
param tags object = {}

/*
** Dependencies
*/
@description('The resource ID of the hosting Azure Container managed environment.')
param environmentId string

@description('The ID of a user-assigned managed identity to use as the identity for this resource. Use a blank string for a system-assigned identity.')
param managedIdentityId string = ''

/*
** Settings
*/

@description('Container images included in the container app.')
param containers container[]

@description('If true, allow HTTP connections to the container app endpoint. If false, HTTP connections are redirected to HTTPS.')
param ingressAllowInsecure bool = false

@description('If true, allow external access to the container app endpoint.')
param ingressExternal bool = false

@description('Container port for ingress traffic.')
param ingressTargetPort int = 80

@description('Credentials for private container registries. Entries defined with secretref reference the secrets configuration object.')
param registries array = []

@description('Scale rule maximum replica count.')
param scaleMaxReplicas int = 5

@description('Scale rule minimum replica count.')
param scaleMinReplicas int = 1

@description('Scale rules for the container app.')
param scaleRules array = []

@description('Workload profile to use for app execution.')
param workloadProfileName string = ''

// ========================================================================
// VARIABLES
// ========================================================================

var identity = !empty(managedIdentityId) ? {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentityId}': {}
}
} : {
type: 'SystemAssigned'
}

// ========================================================================
// AZURE RESOURCES
// ========================================================================

resource containerApp 'Microsoft.App/containerApps@2024-02-02-preview' = {
name: name
location: location
tags: tags
identity: identity

properties: {
environmentId: environmentId
workloadProfileName: workloadProfileName
configuration: {
ingress: {
external: ingressExternal
targetPort: ingressTargetPort
allowInsecure: ingressAllowInsecure
}
registries: !empty(registries) ? registries : null
}
template: {
containers: containers
scale: {
minReplicas: scaleMinReplicas
maxReplicas: scaleMaxReplicas
rules: !empty(scaleRules) ? scaleRules : null
}
}
}
}

// ========================================================================
// OUTPUTS
// ========================================================================

output id string = containerApp.id
output name string = containerApp.name
14 changes: 1 addition & 13 deletions infra/core/identity/container-registry-role-assignments.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,7 @@ targetScope = 'resourceGroup'
** Assigns roles to an Azure Container Registry for the specified identities.
*/

// ========================================================================
// USER-DEFINED TYPES
// ========================================================================

// From: infra/types/ApplicationIdentity.bicep
@description('Type describing an application identity.')
type ApplicationIdentity = {
@description('The ID of the identity')
principalId: string

@description('The type of identity - either ServicePrincipal or User')
principalType: 'ServicePrincipal' | 'User'
}
import { ApplicationIdentity } from '../../types/ApplicationIdentity.bicep'

// ========================================================================
// PARAMETERS
Expand Down
19 changes: 1 addition & 18 deletions infra/core/storage/storage-account.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import { PrivateEndpointSettings } from '../../types/PrivateEndpointSettings.bic
import { DiagnosticSettings } from '../../types/DiagnosticSettings.bicep'
import { ApplicationIdentity } from '../../types/ApplicationIdentity.bicep'

type FirewallRules = {
@description('The list of IP address CIDR blocks to allow access from.')
allowedIpAddresses: string[]
}

// ========================================================================
// PARAMETERS
// ========================================================================
Expand Down Expand Up @@ -73,9 +68,6 @@ param sku object = { name: 'Standard_LRS' }
@description('Determines whether or not trusted azure services are allowed to connect to this account')
param bypass string = 'AzureServices'

@description('The firewall rules to install on the sql-server.')
param firewallRules FirewallRules?

// ========================================================================
// VARIABLES
// ========================================================================
Expand All @@ -91,11 +83,6 @@ var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
var defaultToOAuthAuthentication = false
var dnsEndpointType = 'Standard'


var allowedCidrBlocks = firewallRules != null ? map(firewallRules!.allowedIpAddresses, ipaddr => {
value: ipaddr
}) : []

// ========================================================================
// AZURE RESOURCES
// ========================================================================
Expand All @@ -115,11 +102,7 @@ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = {
dnsEndpointType: dnsEndpointType
minimumTlsVersion: minimumTlsVersion
publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled'
networkAcls: enablePublicNetworkAccess ? {
bypass: bypass
defaultAction: 'Deny'
ipRules: allowedCidrBlocks
} : {
networkAcls: enablePublicNetworkAccess ? null : {
defaultAction:'Deny'
bypass: bypass
}
Expand Down
Loading
Loading