Skip to content

Commit

Permalink
Merge pull request #5588 from dfe-analytical-services/EES-5802-add-se…
Browse files Browse the repository at this point in the history
…cret-headers-between-fuapi-and-app-gateway

Ees 5802 add secret headers between fuapi and app gateway
  • Loading branch information
duncan-at-hiveit authored Feb 7, 2025
2 parents d1cba79 + e8e594e commit 08eaf2a
Show file tree
Hide file tree
Showing 33 changed files with 311 additions and 139 deletions.
4 changes: 2 additions & 2 deletions infrastructure/parameters/dev.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
"value": "data.dev.explore-education-statistics.service.gov.uk"
},
"publicApiUrl": {
"value": "dev.statistics.api.education.gov.uk"
"value": "pp-api.education.gov.uk/statistics-dev"
},
"publicApiDocsUrl": {
"value": "dev.statistics.api.education.gov.uk/docs"
"value": "pp-api.education.gov.uk/statistics-dev/docs"
},
"detailedErrors": {
"value": true
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/parameters/pre-prod.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
"value": "data.pre-production.explore-education-statistics.service.gov.uk"
},
"publicApiUrl": {
"value": "pre-production.statistics.api.education.gov.uk"
"value": "pp-api.education.gov.uk/statistics-preprod"
},
"publicApiDocsUrl": {
"value": "pre-production.statistics.api.education.gov.uk/docs"
"value": "pp-api.education.gov.uk/statistics-preprod/docs"
},
"publicAppGATrackingId": {
"value": "G-8FSLWXTV2W"
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/parameters/prod.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
"value": "data.explore-education-statistics.service.gov.uk"
},
"publicApiUrl": {
"value": "statistics.api.education.gov.uk"
"value": "api.education.gov.uk/statistics"
},
"publicApiDocsUrl": {
"value": "statistics.api.education.gov.uk/docs"
"value": "api.education.gov.uk/statistics/docs"
},
"publicAppGATrackingId": {
"value": "G-9YG8ESXR5Y"
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/parameters/test.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
"value": "data.test.explore-education-statistics.service.gov.uk"
},
"publicApiUrl": {
"value": "test.statistics.api.education.gov.uk"
"value": "pp-api.education.gov.uk/statistics-test"
},
"publicApiDocsUrl": {
"value": "test.statistics.api.education.gov.uk/docs"
"value": "pp-api.education.gov.uk/statistics-test/docs"
},
"detailedErrors": {
"value": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ module apiContainerAppModule '../../components/containerApp.bicep' = {
}
}

module storePublicApiContainerAppPrivateUrl '../../components/keyVaultSecret.bicep' = {
name: 'storePublicApiContainerAppPrivateUrl'
params: {
keyVaultName: resourceNames.existingResources.keyVault
secretName: 'ees-publicapi-public-api-containerapp-private-url'
secretValue: 'https://${apiContainerAppModule.outputs.containerAppFqdn}'
}
}

output containerAppFqdn string = apiContainerAppModule.outputs.containerAppFqdn
output containerAppName string = apiContainerAppModule.outputs.containerAppName
output healthProbePath string = '/health'
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ResourceNames } from '../../types.bicep'

@description('Specifies common resource naming variables.')
param resourceNames ResourceNames

@description('Specifies the location for all resources.')
param location string

@description('Secret header value from FUAPI.')
@secure()
param fuapiSecretValue string

@description('Specifies a set of tags with which to tag the resource in Azure.')
param tagValues object

var policyName = '${resourceNames.sharedResources.appGateway}-public-api-afwp'

module wafPolicyModule '../../components/appGatewayWafPolicy.bicep' = {
name: 'wafPolicy'
params: {
name: policyName
location: location
// Do not define any managed rulesets for this WAF policy. The global WAF policy
// will cover these cases.
managedRuleSets: []
// Add a custom rule to only allow traffic that contains a correct secret
// header value that is included from FUAPI.
customRules: [
{
name: 'fuapisecretnotpresent'
action: 'Block'
matchConditions: [
{
type: 'RequestHeaders'
selector: 'X-FUAPI-Secret'
operator: 'Any'
negateOperator: true
}
]
}
{
name: 'fuapisecretincorrect'
action: 'Block'
matchConditions: [
{
type: 'RequestHeaders'
selector: 'X-FUAPI-Secret'
operator: 'Equal'
negateOperator: true
matchValues: [
fuapiSecretValue
]
}
]
}
]
tagValues: tagValues
}
}

output id string = wafPolicyModule.outputs.id
output name string = policyName
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing
parent: vNet
}

module globalWafPolicyModule '../../components/appGatewayWafPolicy.bicep' = {
name: 'wafPolicy'
params: {
name: '${resourceNames.sharedResources.appGateway}-global-afwp'
location: location
tagValues: tagValues
}
}

module appGatewayModule '../../components/appGateway.bicep' = {
name: 'appGatewayDeploy'
params: {
Expand All @@ -51,6 +60,7 @@ module appGatewayModule '../../components/appGateway.bicep' = {
backends: backends
routes: routes
rewrites: rewrites
globalWafPolicyId: globalWafPolicyModule.outputs.id
alerts: deployAlerts ? {
health: true
responseTime: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ module storeCoreStorageConnectionString '../../components/keyVaultSecret.bicep'
name: 'storeCoreStorageConnectionString'
params: {
keyVaultName: resourceNames.existingResources.keyVault
isEnabled: true
secretName: coreStorageConnectionStringSecretKey
secretValue: coreStorageConnectionString
contentType: 'text/plain'
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,11 @@ module storeDataProcessorPsqlConnectionString '../../components/keyVaultSecret.b
name: 'storeDataProcessorPsqlConnectionString'
params: {
keyVaultName: resourceNames.existingResources.keyVault
isEnabled: true
secretName: dataProcessorPsqlConnectionStringSecretKey
secretValue: createManagedIdentityConnectionString(
connectionStringTemplate,
resourceNames.publicApi.dataProcessorIdentity
)
contentType: 'text/plain'
}
}

Expand All @@ -98,13 +96,11 @@ module storePublisherPsqlConnectionString '../../components/keyVaultSecret.bicep
name: 'storePublisherPsqlConnectionString'
params: {
keyVaultName: resourceNames.existingResources.keyVault
isEnabled: true
secretName: publisherPsqlConnectionStringSecretKey
secretValue: createManagedIdentityConnectionString(
connectionStringTemplate,
resourceNames.existingResources.publisherFunction
)
contentType: 'text/plain'
}
}

Expand All @@ -114,13 +110,11 @@ module storeAdminPsqlConnectionString '../../components/keyVaultSecret.bicep' =
name: 'storeAdminPsqlConnectionString'
params: {
keyVaultName: resourceNames.existingResources.keyVault
isEnabled: true
secretName: adminPsqlConnectionStringSecretKey
secretValue: createManagedIdentityConnectionString(
connectionStringTemplate,
resourceNames.existingResources.adminApp
)
contentType: 'text/plain'
}
}

Expand Down
22 changes: 9 additions & 13 deletions infrastructure/templates/public-api/components/appGateway.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ param routes AppGatewayRoute[]
@description('Rules for how the App Gateway should rewrite URLs')
param rewrites AppGatewayRewriteSet[]

@description('Optional id of a WAF policy to use globally across all listeners in this App Gateway')
param globalWafPolicyId string?

@description('Availability zones in the region that the resource should be accessible from. Defaults to all zones')
param availabilityZones ('1' | '2' | '3') [] = [
'1'
Expand Down Expand Up @@ -109,16 +112,6 @@ resource publicIPAddresses 'Microsoft.Network/publicIPAddresses@2024-01-01' = [f
zones: availabilityZones
}]

// Add a Firewall Policy with OWASP and Bot rulesets, running in Prevention mode.
module wafPolicyModule 'appGatewayWafPolicy.bicep' = {
name: 'wafPolicy'
params: {
name: '${appGatewayName}-afwp'
location: location
tagValues: tagValues
}
}

module pathRulesModule './appGatewayPathRules.bicep' = [for route in routes: {
name: '${route.name}-route-paths'
params: {
Expand Down Expand Up @@ -220,6 +213,9 @@ resource appGateway 'Microsoft.Network/applicationGateways@2024-01-01' = {
}
hostName: site.fqdn
requireServerNameIndication: true
firewallPolicy: site.?wafPolicyName != null ? {
id: resourceId('Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies', site.?wafPolicyName ?? '')
} : null
}
}]
requestRoutingRules: [for (route, index) in routes: {
Expand Down Expand Up @@ -279,9 +275,9 @@ resource appGateway 'Microsoft.Network/applicationGateways@2024-01-01' = {
minCapacity: 0
maxCapacity: 10
}
firewallPolicy: {
id: wafPolicyModule.outputs.id
}
firewallPolicy: globalWafPolicyId != null ? {
id: globalWafPolicyId!
} : null
}
dependsOn: [
publicIPAddresses
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { AppGatewayFirewallPolicyCustomRule } from '../types.bicep'

@description('Specifies the location for all resources')
param location string

@description('Specifies the name of the policy')
param name string

@description('A set of managed rulesets to include alongside the default ruleset')
param defaultRuleSet {
ruleSetType: string
ruleSetVersion: string
} = {
ruleSetType: 'Microsoft_DefaultRuleSet'
ruleSetVersion: '2.1'
}

@description('A set of managed rulesets to include alongside the default ruleset')
param managedRuleSets {
ruleSetType: string
ruleSetVersion: string
}[] = [
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.1'
}
]

@description('A set of custom rules to include alongside the managed rulesets')
param customRules AppGatewayFirewallPolicyCustomRule[] = []

@description('Specifies a set of tags with which to tag the resource in Azure')
param tagValues object

Expand All @@ -23,16 +48,26 @@ resource policy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolic
}
managedRules: {
managedRuleSets: [
{
ruleSetType: 'Microsoft_DefaultRuleSet'
ruleSetVersion: '2.1'
}
{
ruleSetType: 'Microsoft_BotManagerRuleSet'
ruleSetVersion: '1.1'
}
defaultRuleSet
...managedRuleSets
]
}
customRules: map(customRules, (rule, index) => {
name: rule.name
action: rule.action
ruleType: 'MatchRule'
priority: rule.?priority ?? 1 + (index * 5)
matchConditions: map(rule.matchConditions, condition => {
matchVariables: [{
variableName: condition.type
selector: condition.?selector
}]
operator: condition.operator
negationConditon: condition.negateOperator
matchValues: condition.?matchValues ?? []
})

})
}
tags: tagValues
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ module storeADOConnectionStringToKeyVault './keyVaultSecret.bicep' = {
name: '${storageAccountName}ConnectionStringSecretDeploy'
params: {
keyVaultName: keyVaultName
isEnabled: true
secretValue: storageAccountConnectionString
contentType: 'text/plain'
secretName: connectionStringSecretName
}
}
Expand All @@ -178,9 +176,7 @@ module storeAccessKeyToKeyVault './keyVaultSecret.bicep' = {
name: '${storageAccountName}AccessKeySecretDeploy'
params: {
keyVaultName: keyVaultName
isEnabled: true
secretValue: key
contentType: 'text/plain'
secretName: accessKeySecretName
}
}
Expand Down
21 changes: 18 additions & 3 deletions infrastructure/templates/public-api/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ param deployRecoveryVault bool = false
param publicUrls {
contentApi: string
publicSite: string
publicApi: string
publicApiAppGateway: string
}

@description('Specifies whether or not the Data Processor Function App already exists.')
Expand Down Expand Up @@ -361,7 +361,7 @@ module apiAppModule 'application/public-api/publicApiApp.bicep' = if (deployCont
containerAppEnvironmentId: containerAppEnvironmentModule.outputs.containerAppEnvironmentId
containerAppEnvironmentIpAddress: containerAppEnvironmentModule.outputs.containerAppEnvironmentIpAddress
contentApiUrl: publicUrls.contentApi
publicApiUrl: publicUrls.publicApi
publicApiUrl: publicUrls.publicApiAppGateway
publicSiteUrl: publicUrls.publicSite
dockerImagesTag: dockerImagesTag
appInsightsConnectionString: appInsightsModule.outputs.appInsightsConnectionString
Expand All @@ -388,6 +388,20 @@ module docsModule 'application/public-api/publicApiDocs.bicep' = if (deployDocsS

var docsRewriteSetName = '${publicApiResourcePrefix}-docs-rewrites'

resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
name: resourceNames.existingResources.keyVault
}

module publicApiWafPolicyModule 'application/public-api/publicApiWafPolicy.bicep' = {
name: 'publicApiWafPolicyModuleDeploy'
params: {
location: location
resourceNames: resourceNames
fuapiSecretValue: keyVault.getSecret('ees-publicapi-app-gateway-fuapi-header')
tagValues: tagValues
}
}

// Create an Application Gateway to serve public traffic for the Public API Container App.
module appGatewayModule 'application/shared/appGateway.bicep' = if (deployContainerApp && deployDocsSite) {
name: 'appGatewayModuleDeploy'
Expand All @@ -398,7 +412,8 @@ module appGatewayModule 'application/shared/appGateway.bicep' = if (deployContai
{
name: publicApiResourcePrefix
certificateName: '${publicApiResourcePrefix}-certificate'
fqdn: replace(publicUrls.publicApi, 'https://', '')
fqdn: replace(publicUrls.publicApiAppGateway, 'https://', '')
wafPolicyName: publicApiWafPolicyModule.outputs.name
}
]
backends: [
Expand Down
Loading

0 comments on commit 08eaf2a

Please sign in to comment.