Skip to content

Commit

Permalink
Merge pull request #78 from Azure/web_jose
Browse files Browse the repository at this point in the history
Web UI prototype
  • Loading branch information
erjosito authored Dec 2, 2021
2 parents 5138464 + 1d0e78b commit c259e9c
Show file tree
Hide file tree
Showing 17 changed files with 1,441 additions and 0 deletions.
36 changes: 36 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Prototype for web-based checklist

This is a Minimum Viable Product (MVP) for an architecture for web-based checklist reviews. It consists of the following elements:

- A MySQL database
- An Azure Container Instance that will launch 3 containers:
- filldb (init container): creates the required database and tables in the MySQL server, and fills in the data imported from the latest checklist
- fillgraphdb (init container): executes any Azure Resource Graph queries stored in the checklist, and stores the results in the MySQL database
- flask (main container): a flask-based web frontend that allows inspecting the MysQL checklist table, as well as updating the status and comments of each individual checklist item

The `fillgraphdb` container needs to authenticate to Azure to send the Azure Resource Graph queries. There are two options:

- Working today: With Service Principal credentials
- Roadmap: With a [User-Managed Identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview#how-can-i-use-managed-identities-for-azure-resources) with read access to the subscription(s). The `identityId` parameter of the ARM template needs to be provided. Initial tests have shown that the User-Managed Identity is not available in the init containers.

The [Azure CLI deployment script for Service Principals](./arm/deploy_sp.azcli) shows how to create the Service Principal, assign the reader role for the whole subscription, and launch the ARM template to create the MySQL server and the Azure Container Instance (it doesn't store the Service Principal secret in an Azure Key Vault, that would be highly advisable). If you already have the Service Principal, you can deploy the ARM template graphically as well using the button below:

[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Freview-checklists%2Fweb_jose%2Fweb%2Farm%2Ftemplate.json)

The web interface will be available in the public IP address of the ACI container group, on TCP port 5000.

## Further improvements

Since this is only a prototype, there are some aspects not being addressed for the sake of simplicity:

- Figure out why the user-managed identities seem not be reachable from the init containers
- No HTTPS (it could be easily achieved with an nginx sidecar in the ACI container group)
- No authentication (an authentication proxy such as Ambassador could be leveraged for this)
- The network firewall of the MySQL server is fully open (it could be closed down to the ACI egress IP address)
- The UI of the flask container is rather rudimentary, but it shows the basic principles and does live updates to the MySQL database without having to press any "Submit" button
- SSL Enforcement is disabled in the MySQL Server due to `flask-mysql` not using encryption
- Decouple the containers, so that they can be launched independently:
- It should be possible to launch the `fillgraphdb` container at any time, to refresh the Graph results
- It should be possible to restart the `flask` container (web) without having t

Contributions highly appreciated!
62 changes: 62 additions & 0 deletions web/arm/deploy_mi.azcli
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
##############################################
# 1. Retrieve Manged Identity, or create one #
##############################################

id_name=checklistid
id_location=westeurope
id_rg=checklistid
id_id=$(az identity show -n $id_name -g $id_rg --query id -o tsv)
if [[ -z "$id_id" ]]; then
echo "Creating user identity in resource group ${id_rg}..."
az group create -n $id_rg -l $id_location -o none
az identity create -n $id_name -g $id_rg -o none
id_id=$(az identity show -n $id_name -g $id_rg --query id -o tsv)
id_client_id=$(az identity show -n $id_name -g $id_rg --query clientId -o tsv)
subscription_id=$(az account show --query id -o tsv)
scope="/subscriptions/${subscription_id}"
az role assignment create --scope $scope --assignee $id_client_id --role 'Reader' -o none
echo "User-managed identity has been created: $id_id"
else
echo "User-managed identity has been located: $id_id"
fi

####################################################
# 2. Create RG and deploy checklist infrastructure #
####################################################

# Variables
suffix=$RANDOM
rg="checklist${suffix}"
location=westeurope
aci_name="checklistaci${suffix}"
mysql_server_name="mysql${suffix}"
mysql_server_user=$(whoami)
mysql_server_password=$(echo $(tr -dc a-zA-Z0-9 </dev/urandom 2>/dev/null| head -c 12))

# Create RG
echo "Creating resource group ${rg} in ${location}..."
az group create -n $rg -l $location -o none

# Deploy ARM template
echo "Deploying template to resource group ${rg}..."
az deployment group create -n checklist$RANDOM -g $rg --template-file ./template.json --parameters \
"checklistTech=aks" \
"identityId=${id_id}" \
"aciName=${aci_name}" \
"serverName=${mysql_server_name}" \
"administratorLogin=${mysql_server_user}" \
"administratorLoginPassword=${mysql_server_password}"

# Optional: re-run fillgraphdb container to refresh the Graph query results
graph_aci_name="graph${suffix}"
graph_image="erjosito/checklist-fillgraphdb:1.0"
mysql_server_fqdn=$(az mysql server show -n $mysql_server_name -g $rg --query 'fullyQualifiedDomainName' -o tsv)
echo "Creating ACI ${graph_aci_name}..."
az container create -n $graph_aci_name -g $rg --assign-identity "$id_id" --image "$graph_image" --ip-address public --restart-policy OnFailure -o none -e \
"MYSQL_PASSWORD=${mysql_server_password}" \
"MYSQL_USER=${mysql_server_user}" \
"MYSQL_FQDN=${mysql_server_fqdn}" \
"WAIT_INTERVALS=6"

# Cleanup
# az group delete $rg -y --no-wait
100 changes: 100 additions & 0 deletions web/arm/deploy_sp.azcli
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
##################################################################
# 1. Retrieve SP credentials from Azure Key Vault, or create one #
##################################################################

# Variables
keyvault_name=erjositoKeyvault
keyvault_rg=keyvaults # Only required if new AKV is to be created
keyvault_loc=westeurope # Only required if new AKV is to be created
purpose=checklists

# Day zero: create Azure Key Vault if required
echo "Verifying if AKV ${keyvault_name} exists..."
keyvault_rg_found=$(az keyvault list -o tsv --query "[?name=='$keyvault_name'].resourceGroup")
if [[ -n ${keyvault_rg_found} ]]
then
echo "AKV ${keyvault_name} found in resource group $keyvault_rg_found"
keyvault_rg="$keyvault_rg_found"
else
echo "Creating AKV ${keyvault_name} in RG ${keyvault_rg}..."
az group create -n $keyvault_rg -l $keyvault_loc -o none
az keyvault create -n $keyvault_name -g $keyvault_rg -l $keyvault_loc -o none
user_name=$(az account show --query 'user.name' -o tsv)
echo "Setting policies for user ${user_name}..."
az keyvault set-policy -n $keyvault_name -g $keyvault_rg --upn $user_name -o none \
--certificate-permissions backup create delete deleteissuers get getissuers import list listissuers managecontacts manageissuers purge recover restore setissuers update \
--key-permissions backup create decrypt delete encrypt get import list purge recover restore sign unwrapKey update verify wrapKey \
--secret-permissions backup delete get list purge recover restore set \
--storage-permissions backup delete deletesas get getsas list listsas purge recover regeneratekey restore set setsas update
fi

# Try to get SP details from AKV
echo "Trying to get secrets from AKV ${keyvault_name}..."
keyvault_appid_secret_name=$purpose-sp-appid
keyvault_password_secret_name=$purpose-sp-secret
sp_app_id=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_appid_secret_name --query 'value' -o tsv)
sp_app_secret=$(az keyvault secret show --vault-name $keyvault_name -n $keyvault_password_secret_name --query 'value' -o tsv)

# If either variable is blank, create new SP with the required name
if [[ -z "$sp_app_id" ]] || [[ -z "$sp_app_secret" ]]
then
# Create new SP
echo "Service Principal secrets for ${purpose} not found, creating new Service Principal..."
sp_name=$purpose
sp_output=$(az ad sp create-for-rbac --name $sp_name --skip-assignment 2>/dev/null)
sp_app_id=$(echo $sp_output | jq -r '.appId')
sp_app_secret=$(echo $sp_output | jq -r '.password')
az keyvault secret set --vault-name $keyvault_name --name $keyvault_appid_secret_name --value $sp_app_id -o none
az keyvault secret set --vault-name $keyvault_name --name $keyvault_password_secret_name --value $sp_app_secret -o none
# Optionally, assign Azure RBAC roles (example a RG) or AKV policy (example certificate/secret get if the SP should be able to retrieve certs)
echo "Adding Reader role to new Service Principal..."
subscription_id=$(az account show --query id -o tsv)
scope="/subscriptions/${subscription_id}"
az role assignment create --scope $scope --assignee $sp_app_id --role 'Reader' -o none
fi

# Verify whether SP has expired, and if so, renew it
sp_end_date=$(az ad app show --id $sp_app_id --query 'passwordCredentials[0].endDate' -o tsv)
sp_end_date=$(date --date="$sp_end_date" +%s)
now=$(date +%s)
if [[ $sp_end_date < $now ]]
then
echo "SP expired, extending one year"
new_password=$(az ad app credential reset --id $sp_app_id --years 1 --query password -o tsv)
az keyvault secret set --vault-name $keyvault_name --name $keyvault_password_secret_name --value $new_password -o none
sp_app_secret=$new_password
else
echo "SP not expired, not required to renew"
fi

# Optional: test the SP credentials
# tenant_id=$(az account show --query tenantId -o tsv)
# az login --service-principal -u $sp_app_id -p $sp_app_secret --tenant $tenant_id

####################################################
# 2. Create RG and deploy checklist infrastructure #
####################################################

suffix=$RANDOM
rg="checklist${suffix}"
location=westeurope
aci_name="checklistaci${suffix}"
mysql_server_name="mysql${suffix}"
mysql_server_user=$(whoami)
mysql_server_password=$(echo $(tr -dc a-zA-Z0-9 </dev/urandom 2>/dev/null| head -c 12))
echo "Creating resource group ${rg} in ${location}..."
az group create -n $rg -l $location -o none
echo "Deploying template to resource group ${rg}..."
tenant_id=$(az account show --query tenantId -o tsv)
az deployment group create -n checklist$RANDOM -g $rg --template-file ./template.json --parameters \
"checklistTech=aks" \
"tenantId=${tenant_id}" \
"clientId=${sp_app_id}" \
"clientSecret=${sp_app_secret}" \
"aciName=${aci_name}" \
"serverName=${mysql_server_name}" \
"administratorLogin=${mysql_server_user}" \
"administratorLoginPassword=${mysql_server_password}"

# Cleanup
# az group delete $rg -y --no-wait
23 changes: 23 additions & 0 deletions web/arm/parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"identityName": {
"value": "checklistid"
},
"serverName": {
"value": "checklistsql1138"
},
"administratorLogin": {
"value": "jose"
},
"administratorLoginPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/e7da9914-9b05-4891-893c-546cb7b0422e/resourceGroups/myKeyvault/providers/Microsoft.KeyVault/vaults/erjositoKeyvault"
},
"secretName": "defaultPassword"
}
}
}
}
Loading

0 comments on commit c259e9c

Please sign in to comment.