Skip to content

Commit

Permalink
Add KeyVault Access to Sample Application (#39)
Browse files Browse the repository at this point in the history
* Enabled capability for dev-sample to access keyvault secret values.
  • Loading branch information
danielscholl authored Jan 24, 2024
1 parent 9e8285b commit 1d86e5f
Show file tree
Hide file tree
Showing 17 changed files with 286 additions and 262 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,34 @@

## Guiding Principles

The guiding principle of this development approach is to offer a way to facilitate direct engagement with the OSDU codebase on the Azure Cloud in a minimal fashion. This is not recomended for any production use and is purely seen as a method for development and does not come with any type of official support. The approach aligns with two key pillars from the [Azure Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/what-is-well-architected-framework):
The guiding principle of this development approach is to offer a way to facilitate direct engagement with the OSDU codebase on the Azure Cloud in a minimal fashion. This is not recomended for any production use and is purely seen as a method for development and does not come with any type of official support. The approach aligns with three key pillars from the [Azure Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/what-is-well-architected-framework):

1. Cost Optimization -- We aim to create a cost-effective approach, balancing cost along with security considerations.
2. Security -- Our goal is to enhance security levels within the constraints of working to allow for development in a secure manner, working to ensure a zero trust approach to development.
1. __Cost Optimization__ -- We aim to create a cost-effective approach, balancing cost along with security considerations.
2. __Security__ -- Our goal is to enhance security levels within the constraints of working to allow for development in a secure manner, working to ensure a zero trust approach to development.
3. __Operational Excellence__ -- A strong DevOps, Standards, and Automation approach is always thought of first.

To support ease of use, this idea integrates closely with [Github Codespaces](https://github.com/features/codespaces) and the [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/), working to facilitate seamless development and innovation on ideas.

### Desired State

To accomplish the goal of a `minimal` approach the use of a desired state approach is the key driver.

__Bicep for Desired State Configuration__

Bicep is a domain-specific language (DSL) for deploying Azure resources declaratively. It is built on top of Azure Resource Manager (ARM) templates and simplifies the syntax and experience of authoring ARM templates. With Bicep, you define the desired state of your Azure infrastructure in code. This includes specifying the resources you want to deploy, their configurations, and the relationships between resources.

When you deploy a Bicep file, Azure Resource Manager (ARM) processes the file and ensures that the Azure environment matches the defined desired state. If the actual state drifts from the desired state defined in the Bicep file, redeploying the Bicep template can correct this drift, realigning the actual state with the desired state.

__GitOps for Desired State Management__

GitOps extends the desired state management concept to application deployment and operations. It uses Git as a single source of truth for declarative infrastructure and applications. With GitOps, you store the entire state of your infrastructure and applications in Git repositories, and any changes to the state are made through Git commits.

Automated processes or operators continuously monitor the Git repository for changes. When a change is detected, these operators ensure that the actual state of the infrastructure or application (running in environments such as Kubernetes) is updated to match the desired state defined in the Git repository. This approach not only applies to infrastructure (where tools like Bicep define the infrastructure state) but also to application deployment and configuration.

__Combination of Bicep and Gitops__

The combination of Bicep and GitOps is a powerful strategy that allows for a single place where both the infrastructure and software are fully defined, enabling the system itself to work towards achieving the state that has been described. This integrated approach ensures alignment between infrastructure provisioning and application deployment, streamlining and automating the entire development and operational process.


## Setup

Expand Down
177 changes: 96 additions & 81 deletions bicep/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ var commonLayerConfig = {
}



/*
__ _______ _______ .__ __. .___________. __ .___________.____ ____
| | | \ | ____|| \ | | | || | | |\ \ / /
Expand Down Expand Up @@ -148,7 +149,6 @@ module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.2.1' = {
|__| \__| |_______| |__| \__/ \__/ \______/ | _| `._____||__|\__\
*/


/////////////////
// Network Blade
/////////////////
Expand Down Expand Up @@ -597,6 +597,7 @@ module vpnSite 'br/public:avm/res/network/vpn-site:0.1.0' = if (enableVpnGateway
ipAddress: remoteVpnAddress
}
}

module vpnGateway 'br/public:avm/res/network/vpn-gateway:0.1.0' = if (enableVpnGateway) {
name: '${commonLayerConfig.name}-vpn-gateway'
params: {
Expand Down Expand Up @@ -1002,8 +1003,7 @@ module bastionHost 'br/public:avm/res/network/bastion-host:0.1.0' = if (enableBa
| \ / | / ^ \ | ,----'| |__| | | | | \| | | |__
| |\/| | / /_\ \ | | | __ | | | | . ` | | __|
| | | | / _____ \ | `----.| | | | | | | |\ | | |____
|__| |__| /__/ \__\ \______||__| |__| |__| |__| \__| |_______|
|__| |__| /__/ \__\ \______||__| |__| |__| |__| \__| |_______|
*/

@description('Specifies the name of the administrator account of the virtual machine.')
Expand Down Expand Up @@ -1260,6 +1260,7 @@ var partitionLayerConfig = {
}



/*
.______ ___ .______ .___________. __ .___________. __ ______ .__ __. _______.
| _ \ / \ | _ \ | || | | || | / __ \ | \ | | / |
Expand Down Expand Up @@ -1483,7 +1484,7 @@ var serviceLayerConfig = {
*/

module cluster './modules/aks_cluster.bicep' = {
name: 'aks-${serviceLayerConfig.name}'
name: '${serviceLayerConfig.name}-aks-cluster'
params: {
// Basic Details
resourceName: serviceLayerConfig.name
Expand Down Expand Up @@ -1523,49 +1524,6 @@ module cluster './modules/aks_cluster.bicep' = {
}
}

module appIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.1.0' = {
name: '${serviceLayerConfig.name}-user-managed-identity'
params: {
// Required parameters
name: 'id-${replace(serviceLayerConfig.name, '-', '')}${uniqueString(resourceGroup().id, serviceLayerConfig.name)}'
location: location
enableTelemetry: enableTelemetry

federatedIdentityCredentials: [
{
audiences: [
'api://AzureADTokenExchange'
]
issuer: cluster.outputs.aksOidcIssuerUrl
name: '${serviceLayerConfig.name}-user-managed-identity-fed1'
subject: 'system:serviceaccount:default:workload-identity-sa'
}
]

roleAssignments: [
{
roleDefinitionIdOrName: 'Managed Identity Operator'
principalId: stampIdentity.outputs.principalId
principalType: 'ServicePrincipal'
}
]

// Assign Tags
tags: {
layer: serviceLayerConfig.displayName
}
}
}

module stampOperator './modules/operator_rbac.bicep' = {
name: '${serviceLayerConfig.name}-user-managed-identity-operator'
params: {
operatorIdentityName: stampIdentity.outputs.name
identityclientId: appIdentity.outputs.clientId
}
}


/////////////////
// Elastic Configuration
/////////////////
Expand Down Expand Up @@ -1641,9 +1599,76 @@ module espool3 './modules/aks_agent_pool.bicep' = {
}
}

//--------------Helm Install---------------

/////////////////
// Workload Identity Federated Credentials
/////////////////
module appIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.1.0' = {
name: '${serviceLayerConfig.name}-user-managed-identity'
params: {
// Required parameters
name: 'id-${replace(serviceLayerConfig.name, '-', '')}${uniqueString(resourceGroup().id, serviceLayerConfig.name)}'
location: location
enableTelemetry: enableTelemetry

// Only support 1. https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-considerations#concurrent-updates-arent-supported-user-assigned-managed-identities
federatedIdentityCredentials: [{
audiences: [
'api://AzureADTokenExchange'
]
issuer: cluster.outputs.aksOidcIssuerUrl
name: 'federated-ns_default'
subject: 'system:serviceaccount:default:workload-identity-sa'
}]

roleAssignments: [
{
roleDefinitionIdOrName: 'Managed Identity Operator'
principalId: stampIdentity.outputs.principalId
principalType: 'ServicePrincipal'
}
]

// Assign Tags
tags: {
layer: serviceLayerConfig.displayName
}
}
}

// Federated Credentials have to be sequentially added. Ensure depends on.
module federatedCredsDevSample './modules/federated_identity.bicep' = {
name: '${serviceLayerConfig.name}-federated-cred-ns_dev-sample'
params: {
name: 'federated-ns_dev-sample'
audiences: [
'api://AzureADTokenExchange'
]
issuer: cluster.outputs.aksOidcIssuerUrl
userAssignedIdentityName: appIdentity.outputs.name
subject: 'system:serviceaccount:dev-sample:workload-identity-sa'
}
dependsOn: [
appIdentity
]
}

module appRoleAssignments './modules/app_assignments.bicep' = {
name: '${serviceLayerConfig.name}-user-managed-identity-rbac'
params: {
identityprincipalId: appIdentity.outputs.principalId
kvName: keyvault.outputs.name
}
dependsOn: [
federatedCredsDevSample
]
}

/////////////////
// Helm Charts
/////////////////
module helmAppConfigProvider './modules/aks-run-command/main.bicep' = {
name: 'helmAppConfigProvider'
name: '${serviceLayerConfig.name}-helm-appconfig-provider'
params: {
aksName: cluster.outputs.aksClusterName
location: location
Expand All @@ -1660,6 +1685,7 @@ module helmAppConfigProvider './modules/aks-run-command/main.bicep' = {
}



/*
___ .______ .______ ______ ______ .__ __. _______ __ _______
/ \ | _ \ | _ \ / | / __ \ | \ | | | ____|| | / _____|
Expand Down Expand Up @@ -1707,53 +1733,43 @@ module app_config 'modules/app-configuration/main.bicep' = {
// Add Configuration
keyValues: concat(appSettings)
}
dependsOn: [
appRoleAssignments
]
}

@description('The name of the azure keyvault.')
output ENV_CONFIG_ENDPOINT string = app_config.outputs.endpoint

//--------------Config Map---------------
var configMaps = {
workloadIdentityTemplate: '''
devSampleTemplate: '''
values.yaml: |
azureWorkloadIdentity:
tenantId: "{0}"
clientId: "{1}"
serviceAccount:
create: true
name: ""
'''
appConfigTemplate: '''
values.yaml: |
azureWorkloadIdentity:
clientId: "{0}"
appConfiguration:
endpoint: "{1}"
create: false
name: "workload-identity-sa"
azure:
enabled: true
tenantId: "{0}"
clientId: {1}
configEndpoint: {2}
keyvaultName: {3}
'''
}

module workloadIdentityMap './modules/aks-config-map/main.bicep' = if (enableConfigMap) {
name: '${serviceLayerConfig.name}-cluster-workloadidentitymap'
params: {
aksName: cluster.outputs.aksClusterName
location: location
name: 'workload-identity-values'
namespace: 'default'
fileData: [
format(configMaps.workloadIdentityTemplate, subscription().tenantId, appIdentity.outputs.clientId)
]
}
}

module appConfigProviderMap './modules/aks-config-map/main.bicep' = if (enableConfigMap) {
name: '${serviceLayerConfig.name}-cluster-appconfigmap'
module devSampleMap './modules/aks-config-map/main.bicep' = if (enableConfigMap) {
name: '${serviceLayerConfig.name}-cluster-devsample-configmap'
params: {
aksName: cluster.outputs.aksClusterName
location: location
name: 'app-config-values'
name: 'dev-sample-values'
namespace: 'default'
fileData: [
format(configMaps.appConfigTemplate, appIdentity.outputs.clientId, app_config.outputs.endpoint)
format(configMaps.devSampleTemplate,
subscription().tenantId,
appIdentity.outputs.clientId,
app_config.outputs.endpoint,
keyvault.outputs.name)
]
}
}
Expand Down Expand Up @@ -1808,8 +1824,7 @@ module fluxConfiguration 'br/public:avm/res/kubernetes-configuration/flux-config
}
dependsOn: [
app_config
appConfigProviderMap
workloadIdentityMap
devSampleMap
espool1
espool2
espool3
Expand Down
35 changes: 35 additions & 0 deletions bicep/modules/app_assignments.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// param operatorIdentityName string
param identityprincipalId string

@description('The name of the Azure Key Vault')
param kvName string

// resource userIdentity 'Microsoft.Authorization/roleAssignments@2022-04-01' existing = {
// name: operatorIdentityName
// }

// var managedIdentityOperator = resourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830')
// resource identityOperatorRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// scope: userIdentity
// name: guid(userIdentity.id, identityprincipalId, managedIdentityOperator)
// properties: {
// roleDefinitionId: managedIdentityOperator
// principalType: 'ServicePrincipal'
// principalId: identityprincipalId
// }
// }

resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: kvName
}

var keyVaultSecretsUser = resourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
resource kvRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: keyVault
name: guid(identityprincipalId, keyVault.id)
properties: {
roleDefinitionId: keyVaultSecretsUser
principalType: 'ServicePrincipal'
principalId: identityprincipalId
}
}
41 changes: 41 additions & 0 deletions bicep/modules/federated_identity.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
metadata name = 'User Assigned Identity Federated Identity Credential'
metadata description = 'This module deploys a User Assigned Identity Federated Identity Credential.'
metadata owner = 'Azure/module-maintainers'

@description('Conditional. The name of the parent user assigned identity. Required if the template is used in a standalone deployment.')
param userAssignedIdentityName string

@description('Required. The name of the secret.')
param name string

@description('Required. The list of audiences that can appear in the issued token. Should be set to api://AzureADTokenExchange for Azure AD. It says what Microsoft identity platform should accept in the aud claim in the incoming token. This value represents Azure AD in your external identity provider and has no fixed value across identity providers - you might need to create a new application registration in your IdP to serve as the audience of this token.')
param audiences array

@description('Required. The URL of the issuer to be trusted. Must match the issuer claim of the external token being exchanged.')
param issuer string

@description('Required. The identifier of the external software workload within the external identity provider. Like the audience value, it has no fixed format, as each IdP uses their own - sometimes a GUID, sometimes a colon delimited identifier, sometimes arbitrary strings. The value here must match the sub claim within the token presented to Azure AD.')
param subject string

resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
name: userAssignedIdentityName
}

resource federatedIdentityCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = {
name: name
parent: userAssignedIdentity
properties: {
audiences: audiences
issuer: issuer
subject: subject
}
}

@description('The name of the federated identity credential.')
output name string = federatedIdentityCredential.name

@description('The resource ID of the federated identity credential.')
output resourceId string = federatedIdentityCredential.id

@description('The name of the resource group the federated identity credential was created in.')
output resourceGroupName string = resourceGroup().name
Loading

0 comments on commit 1d86e5f

Please sign in to comment.