diff --git a/.azure/applications/graphql/main.bicep b/.azure/applications/graphql/main.bicep index 7fc384267..2d63bebe9 100644 --- a/.azure/applications/graphql/main.bicep +++ b/.azure/applications/graphql/main.bicep @@ -1,5 +1,7 @@ targetScope = 'resourceGroup' +import { Scale } from '../../modules/containerApp/main.bicep' + @description('The tag of the image to be used') @minLength(3) param imageTag string @@ -106,6 +108,34 @@ var probes = [ } ] +@description('The scaling configuration for the container app') +param scale Scale = { + minReplicas: 2 + maxReplicas: 10 + rules: [ + { + name: 'cpu' + custom: { + type: 'cpu' + metadata: { + type: 'Utilization' + value: '70' + } + } + } + { + name: 'memory' + custom: { + type: 'memory' + metadata: { + type: 'Utilization' + value: '70' + } + } + } + ] +} + resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: environmentKeyVaultName } @@ -126,6 +156,7 @@ module containerApp '../../modules/containerApp/main.bicep' = { revisionSuffix: revisionSuffix probes: probes port: port + scale: scale } } diff --git a/.azure/applications/graphql/yt01.bicepparam b/.azure/applications/graphql/yt01.bicepparam new file mode 100644 index 000000000..6b94c4e0b --- /dev/null +++ b/.azure/applications/graphql/yt01.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param apimIp = '51.13.85.197' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/applications/service/main.bicep b/.azure/applications/service/main.bicep new file mode 100644 index 000000000..1d170855f --- /dev/null +++ b/.azure/applications/service/main.bicep @@ -0,0 +1,177 @@ +targetScope = 'resourceGroup' + +@description('The tag of the image to be used') +@minLength(3) +param imageTag string + +@description('The environment for the deployment') +@minLength(3) +param environment string + +@description('The location where the resources will be deployed') +@minLength(3) +param location string + +@description('The suffix for the revision of the container app') +@minLength(3) +param revisionSuffix string + +@description('CPU and memory resources for the container app') +param resources object? + +@description('The name of the container app environment') +@minLength(3) +param containerAppEnvironmentName string + +@description('The name of the Service Bus namespace') +@minLength(3) +param serviceBusNamespaceName string + +@description('The connection string for Application Insights') +@minLength(3) +@secure() +param appInsightConnectionString string + +@description('The name of the App Configuration store') +@minLength(5) +param appConfigurationName string + +@description('The name of the Key Vault for the environment') +@minLength(3) +param environmentKeyVaultName string + +var namePrefix = 'dp-be-${environment}' +var baseImageUrl = 'ghcr.io/digdir/dialogporten-' +var tags = { + Environment: environment + Product: 'Dialogporten' +} + +resource appConfiguration 'Microsoft.AppConfiguration/configurationStores@2023-03-01' existing = { + name: appConfigurationName +} + +resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' existing = { + name: containerAppEnvironmentName +} + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: '${namePrefix}-service-identity' + location: location + tags: tags +} + +var containerAppEnvVars = [ + { + name: 'ASPNETCORE_ENVIRONMENT' + value: environment + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: appInsightConnectionString + } + { + name: 'AZURE_APPCONFIG_URI' + value: appConfiguration.properties.endpoint + } + { + name: 'ASPNETCORE_URLS' + value: 'http://+:8080' + } + { + name: 'AZURE_CLIENT_ID' + value: managedIdentity.properties.clientId + } +] + +resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = { + name: environmentKeyVaultName +} + +var serviceName = 'service' + +var containerAppName = '${namePrefix}-${serviceName}' + +var port = 8080 + +var probes = [ + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Liveness' + httpGet: { + path: '/health/liveness' + port: port + } + } + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Readiness' + httpGet: { + path: '/health/readiness' + port: port + } + } + { + periodSeconds: 5 + initialDelaySeconds: 2 + type: 'Startup' + httpGet: { + path: '/health/startup' + port: port + } + } +] + +module keyVaultReaderAccessPolicy '../../modules/keyvault/addReaderRoles.bicep' = { + name: 'keyVaultReaderAccessPolicy-${containerAppName}' + params: { + keyvaultName: environmentKeyVaultResource.name + principalIds: [managedIdentity.properties.principalId] + } +} + +module appConfigReaderAccessPolicy '../../modules/appConfiguration/addReaderRoles.bicep' = { + name: 'appConfigReaderAccessPolicy-${containerAppName}' + params: { + appConfigurationName: appConfigurationName + principalIds: [managedIdentity.properties.principalId] + } +} + +module serviceBusOwnerAccessPolicy '../../modules/serviceBus/addDataOwnerRoles.bicep' = { + name: 'serviceBusOwnerAccessPolicy-${containerAppName}' + params: { + serviceBusNamespaceName: serviceBusNamespaceName + principalIds: [managedIdentity.properties.principalId] + } +} + +module containerApp '../../modules/containerApp/main.bicep' = { + name: containerAppName + params: { + name: containerAppName + // todo: make this dynamic based on service name. Using webapi for now. + // image: '${baseImageUrl}${serviceName}:${imageTag}' + image: '${baseImageUrl}webapi:${imageTag}' + location: location + envVariables: containerAppEnvVars + containerAppEnvId: containerAppEnvironment.id + tags: tags + resources: resources + probes: probes + port: port + revisionSuffix: revisionSuffix + userAssignedIdentityId: managedIdentity.id + // TODO: Once all container apps use user-assigned identities, remove this comment and ensure userAssignedIdentityId is always provided + } + dependsOn: [ + keyVaultReaderAccessPolicy + appConfigReaderAccessPolicy + serviceBusOwnerAccessPolicy + ] +} + +output name string = containerApp.outputs.name +output revisionName string = containerApp.outputs.revisionName diff --git a/.azure/applications/service/prod.bicepparam b/.azure/applications/service/prod.bicepparam new file mode 100644 index 000000000..b35e25d76 --- /dev/null +++ b/.azure/applications/service/prod.bicepparam @@ -0,0 +1,12 @@ +using './main.bicep' + +param environment = 'prod' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') +// secrets +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') diff --git a/.azure/applications/service/staging.bicepparam b/.azure/applications/service/staging.bicepparam new file mode 100644 index 000000000..f1c8a3305 --- /dev/null +++ b/.azure/applications/service/staging.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'staging' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') + +// secrets +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') diff --git a/.azure/applications/service/test.bicepparam b/.azure/applications/service/test.bicepparam new file mode 100644 index 000000000..87c700860 --- /dev/null +++ b/.azure/applications/service/test.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'test' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') + +// secrets +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') diff --git a/.azure/applications/service/yt01.bicepparam b/.azure/applications/service/yt01.bicepparam new file mode 100644 index 000000000..81261d36f --- /dev/null +++ b/.azure/applications/service/yt01.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') + +// secrets +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') diff --git a/.azure/applications/sync-subject-resource-mappings-job/yt01.bicepparam b/.azure/applications/sync-subject-resource-mappings-job/yt01.bicepparam new file mode 100644 index 000000000..d742f9760 --- /dev/null +++ b/.azure/applications/sync-subject-resource-mappings-job/yt01.bicepparam @@ -0,0 +1,11 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param jobSchedule = '*/5 * * * *' // Runs every 5 minutes + +//secrets +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') diff --git a/.azure/applications/web-api-eu/main.bicep b/.azure/applications/web-api-eu/main.bicep index 9726cc300..9f032afbf 100644 --- a/.azure/applications/web-api-eu/main.bicep +++ b/.azure/applications/web-api-eu/main.bicep @@ -1,5 +1,7 @@ targetScope = 'resourceGroup' +import { Scale } from '../../modules/containerApp/main.bicep' + @description('The tag of the image to be used') @minLength(3) param imageTag string @@ -77,6 +79,34 @@ var containerAppEnvVars = [ } ] +@description('The scaling configuration for the container app') +param scale Scale = { + minReplicas: 2 + maxReplicas: 10 + rules: [ + { + name: 'cpu' + custom: { + type: 'cpu' + metadata: { + type: 'Utilization' + value: '70' + } + } + } + { + name: 'memory' + custom: { + type: 'memory' + metadata: { + type: 'Utilization' + value: '70' + } + } + } + ] +} + resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: environmentKeyVaultName } @@ -128,6 +158,7 @@ module containerApp '../../modules/containerApp/main.bicep' = { resources: resources probes: probes revisionSuffix: revisionSuffix + scale: scale } } diff --git a/.azure/applications/web-api-eu/yt01.bicepparam b/.azure/applications/web-api-eu/yt01.bicepparam new file mode 100644 index 000000000..6b94c4e0b --- /dev/null +++ b/.azure/applications/web-api-eu/yt01.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param apimIp = '51.13.85.197' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/applications/web-api-migration-job/yt01.bicepparam b/.azure/applications/web-api-migration-job/yt01.bicepparam new file mode 100644 index 000000000..ebf1e4731 --- /dev/null +++ b/.azure/applications/web-api-migration-job/yt01.bicepparam @@ -0,0 +1,9 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param imageTag = readEnvironmentVariable('IMAGE_TAG') + +//secrets +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') diff --git a/.azure/applications/web-api-so/yt01.bicepparam b/.azure/applications/web-api-so/yt01.bicepparam new file mode 100644 index 000000000..6b94c4e0b --- /dev/null +++ b/.azure/applications/web-api-so/yt01.bicepparam @@ -0,0 +1,13 @@ +using './main.bicep' + +param environment = 'yt01' +param location = 'norwayeast' +param apimIp = '51.13.85.197' +param imageTag = readEnvironmentVariable('IMAGE_TAG') +param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') + +// secrets +param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') +param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') +param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') +param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') diff --git a/.azure/modules/containerApp/main.bicep b/.azure/modules/containerApp/main.bicep index 7eb404bef..9259e047e 100644 --- a/.azure/modules/containerApp/main.bicep +++ b/.azure/modules/containerApp/main.bicep @@ -31,6 +31,37 @@ param revisionSuffix string @description('The probes for the container app') param probes array = [] +@export() +type ScaleRule = { + name: string + // add additional types as needed: https://keda.sh/docs/2.15/scalers/ + custom: { + type: 'cpu' | 'memory' + metadata: { + type: 'Utilization' + value: string + } + } +} + +@export() +type Scale = { + minReplicas: int + maxReplicas: int + rules: ScaleRule[] +} + +@description('The scaling configuration for the container app') +param scale Scale = { + minReplicas: 1 + maxReplicas: 1 + rules: [] +} + +// TODO: Refactor to make userAssignedIdentityId a required parameter once all container apps use user-assigned identities +@description('The ID of the user-assigned managed identity (optional)') +param userAssignedIdentityId string = '' + // Container app revision name does not allow '.' character var cleanedRevisionSuffix = replace(revisionSuffix, '.', '-') @@ -50,12 +81,19 @@ var ingress = { ipSecurityRestrictions: ipSecurityRestrictions } +var identityConfig = empty(userAssignedIdentityId) ? { + type: 'SystemAssigned' +} : { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } +} + resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { name: name location: location - identity: { - type: 'SystemAssigned' - } + identity: identityConfig properties: { configuration: { ingress: ingress @@ -63,10 +101,7 @@ resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { environmentId: containerAppEnvId template: { revisionSuffix: cleanedRevisionSuffix - scale: { - minReplicas: 1 - maxReplicas: 1 // temp disable scaling for outbox scheduling - } + scale: scale containers: [ { name: name @@ -81,6 +116,10 @@ resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { tags: tags } -output identityPrincipalId string = containerApp.identity.principalId +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(userAssignedIdentityId)) { + name: last(split(userAssignedIdentityId, '/')) +} + +output identityPrincipalId string = empty(userAssignedIdentityId) ? containerApp.identity.principalId : managedIdentity.properties.principalId output name string = containerApp.name output revisionName string = containerApp.properties.latestRevisionName diff --git a/.azure/modules/serviceBus/addDataOwnerRoles.bicep b/.azure/modules/serviceBus/addDataOwnerRoles.bicep new file mode 100644 index 000000000..aa0dd5b1a --- /dev/null +++ b/.azure/modules/serviceBus/addDataOwnerRoles.bicep @@ -0,0 +1,27 @@ +@description('The name of the Service Bus namespace') +param serviceBusNamespaceName string + +@description('Array of principal IDs to assign the Azure Service Bus Data Owner role to') +param principalIds array + +resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2023-01-01-preview' existing = { + name: serviceBusNamespaceName +} + +@description('This is the built-in Azure Service Bus Data Owner role. See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#azure-service-bus-data-owner') +resource serviceBusDataOwnerRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + scope: subscription() + name: '090c5cfd-751d-490a-894a-3ce6f1109419' +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for principalId in principalIds: { + scope: serviceBusNamespace + name: guid(serviceBusNamespace.id, principalId, serviceBusDataOwnerRoleDefinition.id) + properties: { + roleDefinitionId: serviceBusDataOwnerRoleDefinition.id + principalId: principalId + principalType: 'ServicePrincipal' + } + } +] diff --git a/.azure/modules/serviceBus/main.bicep b/.azure/modules/serviceBus/main.bicep index dca532ff6..74607b134 100644 --- a/.azure/modules/serviceBus/main.bicep +++ b/.azure/modules/serviceBus/main.bicep @@ -32,7 +32,7 @@ param sku Sku var serviceBusNameMaxLength = 50 var serviceBusName = uniqueResourceName('${namePrefix}-service-bus', serviceBusNameMaxLength) -resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = { +resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2023-01-01-preview' = { name: serviceBusName location: location sku: sku diff --git a/.coderabbit.yaml b/.coderabbit.yaml index a92ae0b2c..f28f30133 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -13,7 +13,7 @@ reviews: collapse_walkthrough: true sequence_diagrams: false labeling_instructions: [] - path_filters: [] + path_filters: ["!**/Migrations/**/*Designer.cs", "!**/Migrations/DialogDbContextModelSnapshot.cs"] path_instructions: [] abort_on_close: true auto_review: diff --git a/.github/workflows/ci-cd-main.yml b/.github/workflows/ci-cd-main.yml index 8918519ae..350c80019 100644 --- a/.github/workflows/ci-cd-main.yml +++ b/.github/workflows/ci-cd-main.yml @@ -101,6 +101,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: test region: norwayeast diff --git a/.github/workflows/ci-cd-prod.yml b/.github/workflows/ci-cd-prod.yml index 9189675f2..37fefee08 100644 --- a/.github/workflows/ci-cd-prod.yml +++ b/.github/workflows/ci-cd-prod.yml @@ -73,6 +73,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: prod region: norwayeast @@ -96,6 +97,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: prod region: norwayeast diff --git a/.github/workflows/ci-cd-pull-request-release-please.yml b/.github/workflows/ci-cd-pull-request-release-please.yml index 8f559d143..f6a73c4ce 100644 --- a/.github/workflows/ci-cd-pull-request-release-please.yml +++ b/.github/workflows/ci-cd-pull-request-release-please.yml @@ -58,6 +58,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: staging region: norwayeast diff --git a/.github/workflows/ci-cd-pull-request.yml b/.github/workflows/ci-cd-pull-request.yml index 523f85e78..85909d889 100644 --- a/.github/workflows/ci-cd-pull-request.yml +++ b/.github/workflows/ci-cd-pull-request.yml @@ -82,6 +82,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: test region: norwayeast diff --git a/.github/workflows/ci-cd-staging.yml b/.github/workflows/ci-cd-staging.yml index c1a818358..9ae57c024 100644 --- a/.github/workflows/ci-cd-staging.yml +++ b/.github/workflows/ci-cd-staging.yml @@ -65,6 +65,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: staging region: norwayeast diff --git a/.github/workflows/ci-cd-yt01.yml b/.github/workflows/ci-cd-yt01.yml index 10b403330..8dcf2f7df 100644 --- a/.github/workflows/ci-cd-yt01.yml +++ b/.github/workflows/ci-cd-yt01.yml @@ -48,75 +48,75 @@ jobs: region: norwayeast version: ${{ needs.get-current-version.outputs.version }} - # todo: enable when we have infrastructure set up for yt01 - # deploy-apps-yt01: - # name: Deploy apps to yt01 - # needs: - # [get-current-version, check-for-changes, deploy-infra, publish] - # # we want deployment of apps to be dependent on deployment of infrastructure, but if infrastructure is skipped, we still want to deploy the apps - # if: ${{ always() && !failure() && !cancelled() && (github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasBackendChanges == 'true') }} - # uses: ./.github/workflows/workflow-deploy-apps.yml - # secrets: - # AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - # AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - # AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - # # todo: consider resolving these in another way since they are created in the infra-step - # AZURE_RESOURCE_GROUP_NAME: ${{ secrets.AZURE_RESOURCE_GROUP_NAME }} - # AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} - # AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} - # AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} - # AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} - # with: - # environment: yt01 - # region: norwayeast - # version: ${{ needs.get-current-version.outputs.version }} - # runMigration: ${{ github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasMigrationChanges == 'true' }} + deploy-apps: + name: Deploy apps to yt01 + needs: + [get-current-version, check-for-changes, deploy-infra, publish] + # we want deployment of apps to be dependent on deployment of infrastructure, but if infrastructure is skipped, we still want to deploy the apps + if: ${{ always() && !failure() && !cancelled() && (github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasBackendChanges == 'true') }} + uses: ./.github/workflows/workflow-deploy-apps.yml + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + # todo: consider resolving these in another way since they are created in the infra-step + AZURE_RESOURCE_GROUP_NAME: ${{ secrets.AZURE_RESOURCE_GROUP_NAME }} + AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} + AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} + AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} + AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} + with: + environment: yt01 + region: norwayeast + version: ${{ needs.get-current-version.outputs.version }} + runMigration: ${{ github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasMigrationChanges == 'true' }} - # deploy-slack-notifier-yt01: - # name: Deploy slack notifier (yt01) - # needs: [check-for-changes] - # if: ${{ github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasSlackNotifierChanges == 'true' }} - # uses: ./.github/workflows/workflow-deploy-function.yml - # secrets: - # AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - # AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - # AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - # # todo: resolve this automatically, or use tags - # AZURE_FUNCTION_APP_NAME: ${{ secrets.AZURE_SLACK_NOTIFIER_FUNCTION_APP_NAME }} - # with: - # function-app-name: "slack-notifier" - # function-project-path: "./src/Digdir.Tool.Dialogporten.SlackNotifier" - # environment: yt01 + deploy-slack-notifier: + name: Deploy slack notifier (yt01) + needs: [check-for-changes] + if: ${{ github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasSlackNotifierChanges == 'true' }} + uses: ./.github/workflows/workflow-deploy-function.yml + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + # todo: resolve this automatically, or use tags + AZURE_FUNCTION_APP_NAME: ${{ secrets.AZURE_SLACK_NOTIFIER_FUNCTION_APP_NAME }} + with: + function-app-name: "slack-notifier" + function-project-path: "./src/Digdir.Tool.Dialogporten.SlackNotifier" + environment: yt01 - # run-e2e-tests: - # name: "Run K6 functional end-to-end tests" - # # we want the end-to-end tests to be dependent on deployment of infrastructure and apps, but if infrastructure is skipped, we still want to run the tests - # if: ${{ always() && !failure() && !cancelled() && (github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasBackendChanges == 'true') }} - # needs: [deploy-apps-yt01, check-for-changes] - # uses: ./.github/workflows/workflow-run-k6-tests.yml - # secrets: - # TOKEN_GENERATOR_USERNAME: ${{ secrets.TOKEN_GENERATOR_USERNAME }} - # TOKEN_GENERATOR_PASSWORD: ${{ secrets.TOKEN_GENERATOR_PASSWORD }} - # with: - # environment: yt01 - # apiVersion: v1 - # testSuitePath: tests/k6/suites/all-single-pass.js - # permissions: - # checks: write - # pull-requests: write + run-e2e-tests: + name: "Run K6 functional end-to-end tests" + # we want the end-to-end tests to be dependent on deployment of infrastructure and apps, but if infrastructure is skipped, we still want to run the tests + if: ${{ always() && !failure() && !cancelled() && (github.event_name == 'workflow_dispatch' || needs.check-for-changes.outputs.hasBackendChanges == 'true') }} + needs: [deploy-apps, check-for-changes] + uses: ./.github/workflows/workflow-run-k6-tests.yml + secrets: + TOKEN_GENERATOR_USERNAME: ${{ secrets.TOKEN_GENERATOR_USERNAME }} + TOKEN_GENERATOR_PASSWORD: ${{ secrets.TOKEN_GENERATOR_PASSWORD }} + with: + environment: yt01 + apiVersion: v1 + testSuitePath: tests/k6/suites/all-single-pass.js + permissions: + checks: write + pull-requests: write - # send-slack-message-on-failure: - # name: Send Slack message on failure - # needs: [deploy-infra, deploy-apps-yt01, deploy-slack-notifier-yt01, run-e2e-tests, publish] - # if: ${{ always() && failure() && !cancelled() }} - # uses: ./.github/workflows/workflow-send-ci-cd-status-slack-message.yml - # with: - # environment: yt01 - # infra_status: ${{ needs.deploy-infra.result }} - # apps_status: ${{ needs.deploy-apps-yt01.result }} - # slack_notifier_status: ${{ needs.deploy-slack-notifier-yt01.result }} - # e2e_tests_status: ${{ needs.run-e2e-tests.result }} - # publish_status: ${{ needs.publish.result }} - # secrets: - # SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - # SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID_FOR_CI_CD_STATUS }} + send-slack-message-on-failure: + name: Send Slack message on failure + needs: [deploy-infra, deploy-apps, deploy-slack-notifier, run-e2e-tests, publish] + if: ${{ always() && failure() && !cancelled() }} + uses: ./.github/workflows/workflow-send-ci-cd-status-slack-message.yml + with: + environment: yt01 + infra_status: ${{ needs.deploy-infra.result }} + apps_status: ${{ needs.deploy-apps.result }} + slack_notifier_status: ${{ needs.deploy-slack-notifier.result }} + e2e_tests_status: ${{ needs.run-e2e-tests.result }} + publish_status: ${{ needs.publish.result }} + secrets: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID_FOR_CI_CD_STATUS }} diff --git a/.github/workflows/dispatch-apps.yml b/.github/workflows/dispatch-apps.yml index cadcfc066..b4435f72e 100644 --- a/.github/workflows/dispatch-apps.yml +++ b/.github/workflows/dispatch-apps.yml @@ -10,6 +10,7 @@ on: type: choice options: - test + - yt01 - staging - prod version: @@ -54,6 +55,7 @@ jobs: AZURE_CONTAINER_APP_ENVIRONMENT_NAME: ${{ secrets.AZURE_CONTAINER_APP_ENVIRONMENT_NAME }} AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: environment: ${{ inputs.environment }} region: norwayeast diff --git a/.github/workflows/workflow-deploy-apps.yml b/.github/workflows/workflow-deploy-apps.yml index 79a87e76f..0fc5048cf 100644 --- a/.github/workflows/workflow-deploy-apps.yml +++ b/.github/workflows/workflow-deploy-apps.yml @@ -20,6 +20,8 @@ on: required: true AZURE_APP_CONFIGURATION_NAME: required: true + AZURE_SERVICE_BUS_NAMESPACE_NAME: + required: true inputs: region: @@ -145,6 +147,7 @@ jobs: - name: web-api-eu - name: web-api-so - name: graphql + - name: service environment: ${{ inputs.environment }} permissions: id-token: write @@ -174,6 +177,7 @@ jobs: AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: scope: resourcegroup template: ./.azure/applications/${{ matrix.name }}/main.bicep @@ -198,6 +202,7 @@ jobs: AZURE_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.AZURE_APP_INSIGHTS_CONNECTION_STRING }} AZURE_APP_CONFIGURATION_NAME: ${{ secrets.AZURE_APP_CONFIGURATION_NAME }} AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} + AZURE_SERVICE_BUS_NAMESPACE_NAME: ${{ secrets.AZURE_SERVICE_BUS_NAMESPACE_NAME }} with: scope: resourcegroup template: ./.azure/applications/${{ matrix.name }}/main.bicep diff --git a/CHANGELOG.md b/CHANGELOG.md index 7defc6497..11257ee63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [1.25.0](https://github.com/digdir/dialogporten/compare/v1.24.0...v1.25.0) (2024-10-17) + + +### Features + +* **applications:** add scalers for cpu and memory ([#1295](https://github.com/digdir/dialogporten/issues/1295)) ([eb0f19b](https://github.com/digdir/dialogporten/commit/eb0f19bfb5a49da1b4b45a15b6e43785212fc62f)) +* **infrastructure:** create new yt01 app environment ([#1291](https://github.com/digdir/dialogporten/issues/1291)) ([1a1ccc0](https://github.com/digdir/dialogporten/commit/1a1ccc0a81da0be7bf89b105dc3af57ee8ae4e93)) +* **service:** add permissions for service-bus ([#1305](https://github.com/digdir/dialogporten/issues/1305)) ([7bf4177](https://github.com/digdir/dialogporten/commit/7bf41775fa2e1c343972df75d3e4138647fa5742)) +* **service:** deploy application in container apps ([#1303](https://github.com/digdir/dialogporten/issues/1303)) ([a309044](https://github.com/digdir/dialogporten/commit/a309044bd40d9a56c453496aab9122b8f6c67adb)) + + +### Bug Fixes + +* **applications:** add missing property for scale configuration ([3ffb724](https://github.com/digdir/dialogporten/commit/3ffb72476e1085347f51e39e25600bc7a4de69ea)) +* **applications:** use correct scale configuration ([#1311](https://github.com/digdir/dialogporten/issues/1311)) ([b8fb3cc](https://github.com/digdir/dialogporten/commit/b8fb3cc956b5365b4008abc946e4d967fd710efe)) +* Fix ID-porten acr claim parsing ([#1299](https://github.com/digdir/dialogporten/issues/1299)) ([8b8862f](https://github.com/digdir/dialogporten/commit/8b8862fb781a9c57dcd9f3c8315ce66c64d399e2)) +* **service:** ensure default credentials work ([#1306](https://github.com/digdir/dialogporten/issues/1306)) ([b1e6a14](https://github.com/digdir/dialogporten/commit/b1e6a1495e6ca9cd25a6a8cf060f39456db95c30)) + ## [1.24.0](https://github.com/digdir/dialogporten/compare/v1.23.2...v1.24.0) (2024-10-15) diff --git a/README.md b/README.md index 09f6fdc17..9744e5000 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ Ensure you have followed the steps in [Deploying a new infrastructure environmen Use the following steps: -- From the infrastructure resources created, add the following GitHub secrets in the new environment (this will not be necessary in the future as secrets would be added directly from infrastructure deployment): `AZURE_APP_CONFIGURATION_NAME`, `AZURE_APP_INSIGHTS_CONNECTION_STRING`, `AZURE_CONTAINER_APP_ENVIRONMENT_NAME`, `AZURE_ENVIRONMENT_KEY_VAULT_NAME`, `AZURE_REDIS_NAME`, `AZURE_RESOURCE_GROUP_NAME` and `AZURE_SLACK_NOTIFIER_FUNCTION_APP_NAME` +- From the infrastructure resources created, add the following GitHub secrets in the new environment (this will not be necessary in the future as secrets would be added directly from infrastructure deployment): `AZURE_APP_CONFIGURATION_NAME`, `AZURE_APP_INSIGHTS_CONNECTION_STRING`, `AZURE_CONTAINER_APP_ENVIRONMENT_NAME`, `AZURE_ENVIRONMENT_KEY_VAULT_NAME`, `AZURE_REDIS_NAME`, `AZURE_RESOURCE_GROUP_NAME`, `AZURE_SERVICE_BUS_NAMESPACE_NAME` and `AZURE_SLACK_NOTIFIER_FUNCTION_APP_NAME` - Add new parameter files for the environment in all applications `.azure/applications/*/.bicepparam` diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs index abf33f878..28e4f4df8 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs @@ -174,17 +174,26 @@ public static bool TryGetOrganizationNumber(this Claim? consumerClaim, [NotNullW public static bool TryGetAuthenticationLevel(this ClaimsPrincipal claimsPrincipal, [NotNullWhen(true)] out int? authenticationLevel) { - foreach (var claimType in new[] { IdportenAuthLevelClaim, AltinnAuthLevelClaim }) + if (claimsPrincipal.TryGetClaimValue(AltinnAuthLevelClaim, out var claimValue) && int.TryParse(claimValue, out var level)) { - if (!claimsPrincipal.TryGetClaimValue(claimType, out var claimValue)) continue; - // The acr claim value is "LevelX" where X is the authentication level - var valueToParse = claimType == IdportenAuthLevelClaim ? claimValue[5..] : claimValue; - if (!int.TryParse(valueToParse, out var level)) continue; - authenticationLevel = level; return true; } + if (claimsPrincipal.TryGetClaimValue(IdportenAuthLevelClaim, out claimValue)) + { + // The acr claim value is either "idporten-loa-substantial" (previously "Level3") or "idporten-loa-high" (previously "Level4") + // https://docs.digdir.no/docs/idporten/oidc/oidc_protocol_new_idporten#new-acr-values + authenticationLevel = claimValue switch + { + "idporten-loa-substantial" => 3, + "idporten-loa-high" => 4, + _ => null + }; + + return authenticationLevel.HasValue; + } + authenticationLevel = null; return false; } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.yt01.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.yt01.json new file mode 100644 index 000000000..30cf793dc --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.yt01.json @@ -0,0 +1,62 @@ +{ + "Infrastructure": { + "Redis": { + "ConnectionString": "TODO: Add to local secrets" + }, + "DialogDbConnectionString": "TODO: Add to local secrets", + // Settings from appsettings.json, environment variables or other configuration providers. + // The first three are always mandatory for all client definitions types + "Maskinporten": { + // 1. Valid values are test and prod + "Environment": "test", + // 2. Client Id/integration as configured in Maskinporten + "ClientId": "TODO: Add to local secrets", + // 3. Scope(s) requested, space seperated. Must be provisioned on supplied client id. + "Scope": "altinn:events.publish altinn:events.publish.admin altinn:register/partylookup.admin altinn:authorization/authorize.admin altinn:accessmanagement/authorizedparties.admin", + // -------------------------- + // Any additional settings are specific for the selected client definition type. + // See below for examples using other types. + "EncodedJwk": "TODO: Add to local secrets" + }, + "Altinn": { + "BaseUri": "https://platform.yt01.altinn.cloud/", + "EventsBaseUri": "Secret webhook sink URL in source key vault", + "SubscriptionKey": "TODO: Add to local secrets" + } + }, + "Application": { + "Dialogporten": { + "BaseUri": "https://platform.yt01.altinn.cloud/dialogporten", + "Ed25519KeyPairs": { + "Primary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + }, + "Secondary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + } + } + } + }, + "GraphQl": { + "Authentication": { + "JwtBearerTokenSchemas": [ + { + "Name": "Maskinporten", + "WellKnown": "https://test.maskinporten.no/.well-known/oauth-authorization-server/" + }, + { + "Name": "Altinn", + "WellKnown": "https://platform.yt01.altinn.cloud/authentication/api/v1/openid/.well-known/openid-configuration" + }, + { + "Name": "Idporten", + "WellKnown": "https://test.idporten.no/.well-known/openid-configuration" + } + ] + } + } +} \ No newline at end of file diff --git a/src/Digdir.Domain.Dialogporten.Janitor/appsettings.yt01.json b/src/Digdir.Domain.Dialogporten.Janitor/appsettings.yt01.json new file mode 100644 index 000000000..56fdf733d --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Janitor/appsettings.yt01.json @@ -0,0 +1,54 @@ +{ + "Infrastructure": { + "Redis": { + "ConnectionString": "TODO: Add to local secrets" + }, + "DialogDbConnectionString": "TODO: Add to local secrets", + "Maskinporten": { + "Environment": "test", + "ClientId": "TODO: Add to local secrets", + "Scope": "altinn:events.publish altinn:events.publish.admin altinn:register/partylookup.admin altinn:authorization/authorize.admin altinn:accessmanagement/authorizedparties.admin", + "EncodedJwk": "TODO: Add to local secrets" + }, + "Altinn": { + "BaseUri": "https://platform.yt01.altinn.cloud/", + "EventsBaseUri": "Secret webhook sink URL in source key vault", + "SubscriptionKey": "TODO: Add to local secrets" + } + }, + "Application": { + "Dialogporten": { + "BaseUri": "https://platform.yt01.altinn.cloud/dialogporten", + "Ed25519KeyPairs": { + "Primary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + }, + "Secondary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + } + } + } + }, + "WebApi": { + "Authentication": { + "JwtBearerTokenSchemas": [ + { + "Name": "Maskinporten", + "WellKnown": "https://test.maskinporten.no/.well-known/oauth-authorization-server/" + }, + { + "Name": "Altinn", + "WellKnown": "https://platform.yt01.altinn.cloud/authentication/api/v1/openid/.well-known/openid-configuration" + }, + { + "Name": "Idporten", + "WellKnown": "https://test.idporten.no/.well-known/openid-configuration" + } + ] + } + } +} \ No newline at end of file diff --git a/src/Digdir.Domain.Dialogporten.WebApi/appsettings.yt01.json b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.yt01.json new file mode 100644 index 000000000..cf9a9db70 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.yt01.json @@ -0,0 +1,62 @@ +{ + "Infrastructure": { + "Redis": { + "ConnectionString": "TODO: Add to local secrets" + }, + "DialogDbConnectionString": "TODO: Add to local secrets", + // Settings from appsettings.json, environment variables or other configuration providers. + // The first three are always mandatory for all client definitions types + "Maskinporten": { + // 1. Valid values are test and prod + "Environment": "test", + // 2. Client Id/integration as configured in Maskinporten + "ClientId": "TODO: Add to local secrets", + // 3. Scope(s) requested, space seperated. Must be provisioned on supplied client id. + "Scope": "altinn:events.publish altinn:events.publish.admin altinn:register/partylookup.admin altinn:authorization/authorize.admin altinn:accessmanagement/authorizedparties.admin", + // -------------------------- + // Any additional settings are specific for the selected client definition type. + // See below for examples using other types. + "EncodedJwk": "TODO: Add to local secrets" + }, + "Altinn": { + "BaseUri": "https://platform.yt01.altinn.cloud/", + "EventsBaseUri": "Secret webhook sink URL in source key vault", + "SubscriptionKey": "TODO: Add to local secrets" + } + }, + "Application": { + "Dialogporten": { + "BaseUri": "https://platform.yt01.altinn.cloud/dialogporten", + "Ed25519KeyPairs": { + "Primary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + }, + "Secondary": { + "Kid": "TODO: Add to local secrets", + "PrivateComponent": "TODO: Add to local secrets", + "PublicComponent": "TODO: Add to local secrets" + } + } + } + }, + "WebApi": { + "Authentication": { + "JwtBearerTokenSchemas": [ + { + "Name": "Maskinporten", + "WellKnown": "https://test.maskinporten.no/.well-known/oauth-authorization-server/" + }, + { + "Name": "Altinn", + "WellKnown": "https://platform.yt01.altinn.cloud/authentication/api/v1/openid/.well-known/openid-configuration" + }, + { + "Name": "Idporten", + "WellKnown": "https://test.idporten.no/.well-known/openid-configuration" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/Common/Extensions/ClaimsPrincipalExtensionsTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/Common/Extensions/ClaimsPrincipalExtensionsTests.cs new file mode 100644 index 000000000..8c2204bdc --- /dev/null +++ b/tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/Common/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -0,0 +1,59 @@ +using System.Security.Claims; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; + +namespace Digdir.Domain.Dialogporten.Application.Unit.Tests.Features.V1.Common.Extensions; + +public class ClaimsPrincipalExtensionsTests +{ + [Fact] + public void TryGetAuthenticationLevel_Should_Parse_Idporten_Acr_Claim_For_Level3() + { + // Arrange + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("acr", "idporten-loa-substantial") + })); + + // Act + var result = claimsPrincipal.TryGetAuthenticationLevel(out var authenticationLevel); + + // Assert + Assert.True(result); + Assert.Equal(3, authenticationLevel); + } + + [Fact] + public void TryGetAuthenticationLevel_Should_Parse_Idporten_Acr_Claim_For_Level4() + { + // Arrange + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("acr", "idporten-loa-high") + })); + + // Act + var result = claimsPrincipal.TryGetAuthenticationLevel(out var authenticationLevel); + + // Assert + Assert.True(result); + Assert.Equal(4, authenticationLevel); + } + + [Fact] + public void TryGetAuthenticationLevel_Should_Parse_Altinn_Authlevel_First() + { + // Arrange + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("acr", "idporten-loa-high"), + new Claim("urn:altinn:authlevel", "5") + })); + + // Act + var result = claimsPrincipal.TryGetAuthenticationLevel(out var authenticationLevel); + + // Assert + Assert.True(result); + Assert.Equal(5, authenticationLevel); + } +} diff --git a/version.txt b/version.txt index 53cc1a6f9..ad2191947 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.24.0 +1.25.0