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

feat: Add SNI enablement for custom hostnames #9

Merged
merged 4 commits into from
Jul 26, 2024
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
4 changes: 3 additions & 1 deletion ocpp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ using 'main.bicep'
param pubsubKeyVaultCertName = '<KeyVault name of the SSL Certificate for the pub sub service>'
param webKeyVaultCertName ='<KeyVault name of the SSL Certificate for the web test service, can be the same cert if you have a wildcard one>'
param keyVaultName = '<Name of the KeyVault>'
param keyVaultIdentityRG ='<NAME OF THE Managed Identity that has cert read access rights in the KeyVault>'
param keyVaultRG ='<Name of the Resource Group of the KeyVault>'
param keyVaultIdentityName = '<NAME OF THE Managed Identity that has cert read access rights in the KeyVault>'
param keyVaultIdentityRG ='<NAME OF THE Resource Group of the Managed Identity>'
param customDnsZoneName ='<RESOURCE GROUP OF THE MANAGED IDENTITY>'
param pubsubARecordName ='<SUBDOMAIN NAME USED FOR THE WEB PUBSUB SERVICE, EX: wss (for wss.mydomain.com)>'
param dnsZoneRG ='<NAME OF THE RG WHERE THE DNS SERVICE>'
Expand Down
38 changes: 37 additions & 1 deletion ocpp-server/infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ param pubsubKeyVaultCertName string
param webKeyVaultCertName string
@description('Name of the Key Vault where the certificates are stored')
param keyVaultName string
@description('Resource Group name where the Key Vault was created')
param keyVaultRG string
@description('User Assigned Managed Identity name with Get permissions fo the Key Vault Certificate')
param keyVaultIdentityName string
@description('Resource Group name where the User Assigned Managed Identity was created')
param keyVaultIdentityRG string = resourceGroup().name
param keyVaultIdentityRG string = keyVaultRG
@description('Custom DNS Zone Name used for publishing the Web PubSub service endpoint securely')
param customDnsZoneName string
@description('A Record Name for the Web PubSub service endpoint, used as prefix of the DnsZoneName')
Expand All @@ -23,11 +25,20 @@ param pubSubHubName string = 'OcppService'
var pubsubHostName = '${pubsubARecordName}.${customDnsZoneName}'
var webHostName = '${webARecordName}.${customDnsZoneName}'

// Add a Nat Gateway for outbound access
module natgw './modules/natgw.bicep' = {
name: 'natGwService'
params: {
natGwName: 'natgw-${uniqueString(resourceGroup().id)}'
location: resourceGroup().location
}
}
// Creates a VNet with 3 subnets: default, gateway and private endpoints
module virtualNetwork './modules/virtualNetwork.bicep' = {
name: 'vNet'
params: {
virtualNetworkName: 'vnet-${uniqueString(resourceGroup().id)}'
natGatewayId: natgw.outputs.natGatewayId
}
}

Expand Down Expand Up @@ -84,6 +95,17 @@ module webPrivateEndpoint './modules/privateEndpoint.bicep' = {
}
}

module storagePrivateEndpoint './modules/privateEndpoint.bicep' = {
name: 'webStoragePrivateEndpoint'
params: {
privateLinkResource: webApp.outputs.storageAccountId
subnetId: virtualNetwork.outputs.privateSubnetId
vnetId: virtualNetwork.outputs.vnetId
targetSubResource: 'blob'
endpointName: 'webStoragePrivate${uniqueString(resourceGroup().id)}'
}
}

module appGw './modules/appgw.bicep' = {
name: 'appGwService'
params: {
Expand All @@ -96,6 +118,7 @@ module appGw './modules/appgw.bicep' = {
webHostName: webHostName
pubsubHostName: pubsubHostName
keyVaultName: keyVaultName
keyVaultRG: keyVaultRG
keyVaultIdentityName: keyVaultIdentityName
keyVaultIdentityRG: keyVaultIdentityRG
webServiceName: webApp.outputs.webSiteName
Expand Down Expand Up @@ -123,3 +146,16 @@ module wwwdns './modules/dns.bicep' = if (customDnsZoneName != '') {
ipTargetResourceId: appGw.outputs.publicIPAddressId
}
}

module customDomain 'modules/customWebName.bicep' = if (customDnsZoneName != '') {
name: 'customDomain'
params: {
dnszoneName: customDnsZoneName
dnsZoneRG: dnsZoneRG
subdomain: 'www'
webSiteName: webApp.outputs.webSiteName
keyVaultName: keyVaultName
keyVaultRG: keyVaultRG
webKeyVaultCertName: webKeyVaultCertName
}
}
4 changes: 3 additions & 1 deletion ocpp-server/infra/main.parameters.bicepparam.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ using 'main.bicep'
param pubsubKeyVaultCertName = '<KeyVault name of the SSL Certificate for the pub sub service>'
param webKeyVaultCertName ='<KeyVault name of the SSL Certificate for the web test service, can be the same cert if you have a wildcard one>'
param keyVaultName = '<Name of the KeyVault>'
param keyVaultIdentityRG ='<NAME OF THE Managed Identity that has cert read access rights in the KeyVault>'
param keyVaultRG ='<Name of the Resource Group of the KeyVault>'
param keyVaultIdentityName = '<NAME OF THE Managed Identity that has cert read access rights in the KeyVault>'
param keyVaultIdentityRG ='<NAME OF THE Resource Group of the Managed Identity>'
param customDnsZoneName ='<RESOURCE GROUP OF THE MANAGED IDENTITY>'
param pubsubARecordName ='<SUBDOMAIN NAME USED FOR THE WEB PUBSUB SERVICE, EX: wss (for wss.mydomain.com)>'
param dnsZoneRG ='<NAME OF THE RG WHERE THE DNS SERVICE>'
26 changes: 21 additions & 5 deletions ocpp-server/infra/modules/appgw.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ param skuCapacity int = 1
param pubsubHostName string
param webHostName string
param keyVaultName string
param keyVaultRG string
param keyVaultIdentityName string
param keyVaultIdentityRG string
param webServiceName string
Expand All @@ -25,6 +26,7 @@ var ocppRuleSetName = 'ocppRuleSet'
var pubsubBackendPoolName = 'pubsubBackend'
var pubsubBackendSettingsName = 'pubsubBackendSettings'
var pubsubProbeName = 'pubsubProbe'
var webProbeName = 'webProbe'
var pubsubListenerName = 'pubsubListener'
var webBackendPoolName = 'webBackend'
var webListenerName = 'webListener'
Expand All @@ -36,7 +38,7 @@ var isWildcard = (webKeyVaultCertName == pubsubKeyVaultCertName)

resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: keyVaultName
scope: resourceGroup(keyVaultIdentityRG)
scope: resourceGroup(keyVaultRG)
}

resource pubsubKeyVaultCertificate 'Microsoft.KeyVault/vaults/secrets@2024-04-01-preview' existing = {
Expand Down Expand Up @@ -162,6 +164,18 @@ resource appGw 'Microsoft.Network/applicationGateways@2023-02-01' = {
}
}
}
{
name: webProbeName
properties: {
protocol: 'Https'
host: webHostName
path: '/'
interval: 30
timeout: 30
unhealthyThreshold: 3
pickHostNameFromBackendHttpSettings: false
}
}
]
backendHttpSettingsCollection: [
{
Expand All @@ -180,11 +194,13 @@ resource appGw 'Microsoft.Network/applicationGateways@2023-02-01' = {
{
name: webBackendSettingsName
properties: {
port: 80
protocol: 'Http'
port: 443
protocol: 'Https'
cookieBasedAffinity: 'Disabled'
pickHostNameFromBackendAddress: true
requestTimeout: 180 //default KeepAliveInterval for websockets is 2 minutes, setting 3 minutes for app gateway
pickHostNameFromBackendAddress: false
probe: {
id: resourceId('Microsoft.Network/applicationGateways/probes', appgwName, webProbeName)
}
}
}
]
Expand Down
75 changes: 75 additions & 0 deletions ocpp-server/infra/modules/customWebName.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
param dnszoneName string
param dnsZoneRG string
param subdomain string = 'www'
param webSiteName string
param keyVaultName string
param keyVaultRG string
param webKeyVaultCertName string
param location string = resourceGroup().location

var fqdn = '${subdomain}.${dnszoneName}'

resource site 'Microsoft.Web/sites@2023-12-01' existing = {
name: webSiteName
}

resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: keyVaultName
scope: resourceGroup(keyVaultRG)
}

resource webKeyVaultCertificate 'Microsoft.KeyVault/vaults/secrets@2024-04-01-preview' existing = {
name: webKeyVaultCertName
parent: keyVault
}

// Add a TXT record to the DNS zone to verify the custom domain
module verification 'dnstxt.bicep' = {
name: 'dnsServiceWebTxt'
scope: resourceGroup(dnsZoneRG)
params: {
dnszoneName: dnszoneName
subdomain: 'asuid.${fqdn}'
value: site.properties.customDomainVerificationId
}
}

// Enabling Managed certificate for a webapp requires 3 steps
// 1. Add custom domain to webapp with SSL in disabled state
// 2. Upload certificate for the domain
// 3. enable SSL
//
// The last step requires deploying again Microsoft.Web/sites/hostNameBindings - and ARM template forbids this in one deplyment, therefore we need to use modules to chain this.

resource appCustomHost 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = {
name: fqdn
parent: site
dependsOn: [verification]
properties: {
hostNameType: 'Verified'
sslState: 'Disabled'
customHostNameDnsRecordType: 'CName'
siteName: site.name
}
}

resource appCustomHostCertificate 'Microsoft.Web/certificates@2020-06-01' = {
name: fqdn
location: location
dependsOn: [appCustomHost]
properties: any({
keyVaultId: keyVault.id
keyVaultSecretName: webKeyVaultCertificate.name
serverFarmId: site.properties.serverFarmId
})
}

// we need to use a module to enable sni, as ARM forbids using resource with this same type-name combination twice in one deployment.
module appCustomHostEnable './sni-enable.bicep' = {
name: '${deployment().name}-${fqdn}-sni-enable'
params: {
appName: site.name
appHostname: appCustomHostCertificate.name
certificateThumbprint: appCustomHostCertificate.properties.thumbprint
}
}
20 changes: 20 additions & 0 deletions ocpp-server/infra/modules/dnstxt.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
param dnszoneName string
param subdomain string = 'www'
param value string

resource dnsZone 'Microsoft.Network/dnszones@2023-07-01-preview' existing = {
name: dnszoneName
}

resource dnsZoneNewTXTRecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = {
parent: dnsZone
name: subdomain
properties: {
TTL: 300
TXTRecords: [
{
value: [value]
}
]
}
}
34 changes: 34 additions & 0 deletions ocpp-server/infra/modules/natgw.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
param natGwName string
param sku string = 'Standard'
param tier string = 'Regional'
param idleTimeoutInMinutes int = 4
param location string = resourceGroup().location

resource publicIpPrefixes 'Microsoft.Network/publicIPPrefixes@2023-11-01' = {
name: 'ipPrefixes-${natGwName}'
location: location
sku: {
name: sku
tier: tier
}
properties: {
prefixLength: 28
publicIPAddressVersion: 'IPv4'
natGateway: {
id: natGateway.id
}
}
}

resource natGateway 'Microsoft.Network/natGateways@2023-11-01' = {
name: natGwName
location: location
sku: {
name: sku
}
properties: {
idleTimeoutInMinutes: idleTimeoutInMinutes
}
}

output natGatewayId string = natGateway.id
2 changes: 2 additions & 0 deletions ocpp-server/infra/modules/privateEndpoint.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ param privateLinkResource string
@allowed([
'webpubsub'
'sites'
'blob'
])
param targetSubResource string

var dnsByTarget = {
webpubsub: 'privatelink.webpubsub.azure.com'
sites: 'privatelink.azurewebsites.net'
blob: 'privatelink.blob.${environment().suffixes.storage}'
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = {
Expand Down
11 changes: 11 additions & 0 deletions ocpp-server/infra/modules/sni-enable.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
param appName string
param appHostname string
param certificateThumbprint string

resource appCustomHostEnable 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = {
name: '${appName}/${appHostname}'
properties: {
sslState: 'SniEnabled'
thumbprint: certificateThumbprint
}
}
4 changes: 2 additions & 2 deletions ocpp-server/infra/modules/storage.bicep
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
param name string = 'storage${uniqueString(resourceGroup().id)}'
param name string = 'storage${uniqueString(resourceGroup().id)}'
param location string = resourceGroup().location
param sku string = 'Standard_LRS'
param kind string = 'StorageV2'
Expand Down Expand Up @@ -35,5 +35,5 @@ resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/container
}

output storageAccountName string = storageAccount.name
output storageAccountId string = storageAccount.id
output blobContaineId string = blobContainer.id

12 changes: 11 additions & 1 deletion ocpp-server/infra/modules/virtualNetwork.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ param privateSubnetName string = 'private-endpoints'
param privateSubnetPrefix string = '10.1.1.0/24'
param gatewaySubnetName string = 'gateway'
param gatewaySubnetPrefix string = '10.1.2.0/24'
param natGatewayId string

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-09-01' = {
name: virtualNetworkName
Expand All @@ -20,25 +21,34 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-09-01' = {
name: subnetName
properties: {
addressPrefix: subnetPrefix
natGateway: {
id: natGatewayId
}
delegations: [
{
name: 'Microsoft.Web/serverFarms'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
]
}
}
{
name: privateSubnetName
properties: {
natGateway: {
id: natGatewayId
}
addressPrefix: privateSubnetPrefix
}
}
{
name: gatewaySubnetName
properties: {
natGateway: {
id: natGatewayId
}
addressPrefix: gatewaySubnetPrefix
}
}
Expand Down
Loading