Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions IaC/app-registration.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
extension microsoftGraph
param applicationName string = 'template-fastapi-react'
param repositoryName string = 'template-fastapi-react'

// The Entra ID application
// Resource format https://learn.microsoft.com/en-us/graph/templates/reference/applications?view=graph-bicep-1.0
resource app 'Microsoft.Graph/[email protected]' = {
displayName: '${applicationName}'
signInAudience: 'AzureADMyOrg'
uniqueName: '${applicationName}'
spa: {
// The callback URL is the URL that the user is redirected to after the login,
// and it contains the URL of the application that is registered in Radix and localhost for doing development.
redirectUris: [
// Development
'https://proxy-${applicationName}-dev.radix.equinor.com/api/docs/oauth2-redirect'
'https://proxy-${applicationName}-dev.radix.equinor.com'
'https://proxy-${applicationName}-dev.radix.equinor.com/'
// Staging
'https://proxy-${applicationName}-staging.radix.equinor.com/api/docs/oauth2-redirect'
'https://proxy-${applicationName}-staging.radix.equinor.com'
'https://proxy-${applicationName}-staging.radix.equinor.com/'
// Production
'https://${applicationName}.app.radix.equinor.com/api/docs/oauth2-redirect'
'https://proxy-${applicationName}-prod.radix.equinor.com/'
'https://${applicationName}.app.radix.equinor.com/'
'https://${applicationName}.app.radix.equinor.com'
// For development
'http://localhost/api/docs/oauth2-redirect'
'http://localhost/'
'http://localhost:5000/docs/oauth2-redirect'
]
}
api: {
// In version 2 the audience is always the client id, and does not contain the api:// in the decoded JWT.
// It is important to know this because the API expects a JWT token with a specific signature for validation,
// and this is specified in the configuration settings and must match.
requestedAccessTokenVersion: 2
// To allow OpenAPI and clients to talk to the API, we need to add the scope to the API.
oauth2PermissionScopes: [
{
id: '31a61854-0d6d-4c60-918b-efffd4fac373'
adminConsentDescription: 'Allow users to access the API'
adminConsentDisplayName: 'Read'
isEnabled: true
type: 'User'
userConsentDescription: 'Access the API'
userConsentDisplayName: 'Access the API'
value: 'api${app.appId}'
}
]
}
appRoles: [
{
id: '31a61854-0d6d-4c60-918b-efffd4fac379'
allowedMemberTypes: [
'User'
'Application'
]
description: '${applicationName} administrators. Access to all fields. Permission to edit admin values.'
displayName: 'Admin'
isEnabled: true
value: 'admin'
}
]
// Resource format https://learn.microsoft.com/en-us/graph/templates/reference/federatedidentitycredentials?view=graph-bicep-1.0
resource githubFic 'federatedIdentityCredentials' = {
name: '${app.uniqueName}/githubFic'
audiences: [
'api://AzureADTokenExchange'
]
description: 'Federated Identity Credentials for Github Actions to access Entra protected resources'
issuer: 'https://token.actions.githubusercontent.com'
// Subject is checked before issuing an Entra ID access token to access Azure resources.
// GitHub Actions subject examples can be found in https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims
subject: 'repo:equinor/${repositoryName}:ref:refs/heads/main'
}
}

// The Service Principle (or Enterprise App)
resource appSP 'Microsoft.Graph/[email protected]' = {
appId: app.appId
displayName: '${applicationName}'

}
5 changes: 5 additions & 0 deletions IaC/bicepconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"experimentalFeaturesEnabled": {
"extensibility": true
}
}
68 changes: 68 additions & 0 deletions IaC/exceptionEmailNotification.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* Example of separate deployment that uses existing resource.
E.g.: sending email notifications on exceptions in (existing) Application Insights.
Needs to be deployed on resource group level:
az deployment group create --resource-group template-fastapi-react-dev --template-file ./exceptionEmailNotifications.bicep --parameters environment=staging
*/
resource appInsight 'Microsoft.Insights/components@2020-02-02' existing = {
name: '${resourceGroup().name}-logs'
}

resource sendEmailActionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = {
name: 'send-email-action-group'
location: 'global'
properties: {
groupShortName: 'ErrorNotify'
enabled: true
emailReceivers: [
{
name: 'Notify Chris by email_-EmailAction-'
emailAddress: '[email protected]'
useCommonAlertSchema: false
}
{
name: 'Notify Eirik by email_-EmailAction-'
emailAddress: '[email protected]'
useCommonAlertSchema: false
}
]
}
}


resource metricAlerts 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: 'Send email on error in template-fastapi-react'
location: 'global'
properties: {
description: 'When an error is detected in template-fastapi-react, an email is dispatched'
severity: 1
enabled: true
scopes: [
appInsight.id
]
evaluationFrequency: 'PT1H'
windowSize: 'PT1H'
criteria: {
allOf: [
{
threshold: 0
name: 'Metric1'
metricNamespace: 'microsoft.insights/components'
metricName: 'exceptions/count'
operator: 'GreaterThan'
timeAggregation: 'Count'
skipMetricValidation: false
criterionType: 'StaticThresholdCriterion'
}
]
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
}
autoMitigate: false
targetResourceType: 'microsoft.insights/components'
targetResourceRegion: 'norwayeast'
actions: [
{
actionGroupId: sendEmailActionGroup.id
}
]
}
}
24 changes: 24 additions & 0 deletions IaC/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
targetScope='subscription'

@allowed([ 'dev', 'staging', 'prod' ])
param environment string
@description('Specifies the location for resources.')
param resourceGroupLocation string = 'norwayeast'
@description('Create admin password for the database. Will be stored in the KeyVault')
@secure()
param postgresDBPassword string

resource newRG 'Microsoft.Resources/resourceGroups@2024-03-01' = {
name: 'template-fastapi-react-${environment}'
location: resourceGroupLocation
}

module resources 'resources.bicep' = {
name: 'template-fastapi-react-${environment}-resources'
scope: newRG
params: {
storageLocation: resourceGroupLocation
environment: environment
postgresDBPassword: postgresDBPassword
}
}
187 changes: 187 additions & 0 deletions IaC/resources.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
param storageLocation string
param environment string
@secure()
param postgresDBPassword string

resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: 'template-fastapi-react-${environment}-logWorkspace'
location: storageLocation
properties: {
publicNetworkAccessForQuery: 'Enabled'
publicNetworkAccessForIngestion: 'Enabled'
forceCmkForQuery: false
sku: {
name: 'pergb2018'
}
retentionInDays: environment == 'prod' ? 730 : 90
workspaceCapping: {
dailyQuotaGb: environment == 'prod' ? 10 : 1
}
}
}

resource appInsight 'Microsoft.Insights/components@2020-02-02' = {
name: 'template-fastapi-react-${environment}-logs'
location: storageLocation
kind: 'web'
properties: {
Application_Type: 'web'
Flow_Type: 'Bluefield'
IngestionMode: 'LogAnalytics'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
Request_Source: 'rest'
RetentionInDays: environment == 'prod' ? 730 : 90
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}

resource queryPack 'Microsoft.OperationalInsights/queryPacks@2019-09-01' = {
location: storageLocation
name: 'template-fastapi-react-${environment}-queryPack'
properties: {

}
}


resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'template-fastapi-react-${environment}-keyVault'
location: storageLocation
properties: {
tenantId: '3aa4a235-b6e2-48d5-9195-7fcf05b459b0'
softDeleteRetentionInDays: 30
enabledForDeployment: true
enableSoftDelete: true
accessPolicies: [] // Grant each user explicit access after the vault has been created
sku: {
name: 'standard'
family: 'A'
}
publicNetworkAccess: 'Disabled'
}
}

resource databasePassword 'Microsoft.KeyVault/vaults/secrets@2024-04-01-preview' = {
parent: keyVault
name: 'template-fastapi-react-database-${environment}-password'
properties: {
value: postgresDBPassword
}
}


resource sqlServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-preview' = {
name: 'template-fastapi-react-${environment}-database'
location: storageLocation
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
properties: {
version: '16'
administratorLogin: 'template-fastapi-react'
administratorLoginPassword: postgresDBPassword
maintenanceWindow: {
customWindow: 'Enabled'
dayOfWeek: 0
startHour: 3
startMinute: 18
}
network:{publicNetworkAccess: 'Enabled'}
highAvailability: {
mode: 'Disabled'
}
storage: {
storageSizeGB: 64
type: 'Premium_LRS'
}
backup: {
backupRetentionDays: 7
geoRedundantBackup: 'Disabled'
}
}
}

resource template-fastapi-reactDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = {
name: 'template-fastapi-react'
parent: sqlServer
properties: {
charset: 'UTF8'
collation: 'en_US.utf8'
}
}


resource databaseAllowRadixConnection 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = {
name: 'allow-radix-connection'
parent: sqlServer
properties: {
startIpAddress: '52.178.214.192'
endIpAddress: '52.178.214.199'
}
}

resource databaseAllowRadixConnection2 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = {
name: 'allow-radix-connection2'
parent: sqlServer
properties: {
startIpAddress: '137.135.191.80'
endIpAddress: '137.135.191.95'
}
}

resource sendEmailActionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = {
name: 'send-email-action-group'
location: 'global'
properties: {
groupShortName: 'ErrorNotify'
enabled: true
emailReceivers: [
{
name: 'Notify Eirik by email_-EmailAction-'
emailAddress: '[email protected]'
useCommonAlertSchema: false
}
]
}
}


resource metricAlerts 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: 'Send email on error in template-fastapi-react'
location: 'global'
properties: {
description: 'When an error is detected in template-fastapi-react, an email is dispatched'
severity: 1
enabled: true
scopes: [
appInsight.id
]
evaluationFrequency: 'PT1H'
windowSize: 'PT1H'
criteria: {
allOf: [
{
threshold: 0
name: 'Metric1'
metricNamespace: 'microsoft.insights/components'
metricName: 'exceptions/count'
operator: 'GreaterThan'
timeAggregation: 'Count'
skipMetricValidation: false
criterionType: 'StaticThresholdCriterion'
}
]
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
}
autoMitigate: false
targetResourceType: 'microsoft.insights/components'
targetResourceRegion: 'norwayeast'
actions: [
{
actionGroupId: sendEmailActionGroup.id
}
]
}
}
Loading