This repository deploys a small Flask application to Azure App Service. The app uses App Service Authentication with Microsoft Entra ID for sign-in, stores login events in Azure SQL Database, and connects to the database with the web app's system-assigned managed identity instead of a SQL username and password.
The default Terraform configuration targets free Azure options where available: the App Service plan defaults to F1, and the Azure SQL database defaults to the Azure SQL free offer settings. In an eligible subscription and while staying within the free monthly limits, the default sample deployment should not incur cost.
Use this repo when you want to:
- Provision the Azure infrastructure with Terraform
- Deploy or redeploy the Flask app package
- Run the daemon/API verification script against a deployed environment
The functional contract for the app and infrastructure lives in docs/spec.md.
Install these tools locally before you deploy:
bashpython3.12withvenv- Azure CLI
- Terraform 1.6+
sqlcmdfrom go-sqlcmd or another build that supports--authentication-method ActiveDirectoryDefaultzipfor app package deploymentcurlandjqfor the daemon API test script
The Terraform deployment uses sqlcmd during terraform apply to create Azure SQL contained database users for Microsoft Entra principals and grant database roles. The web app managed identity is included by default. There is no separate manual post-provision SQL step in the normal flow anymore.
The identity that runs terraform apply needs both Azure resource permissions and Microsoft Entra application-management permissions:
- Azure subscription or resource-group scope:
ContributorplusUser Access Administrator, orOwner
- Azure Key Vault data plane:
Key Vault Secrets Officer
- Microsoft Entra ID:
Application Administrator, orCloud Application Administrator
Notes:
- Terraform creates Azure resources and also creates Key Vault role assignments, so plain
Contributoris not enough on its own. - Terraform writes generated client secrets into Azure Key Vault, so the deployment identity also needs
Key Vault Secrets Officeron the target vault for data-plane secret management. - Terraform creates app registrations, service principals, app roles, client secrets, and app role assignments in Microsoft Entra ID.
- By default, the identity running Terraform is also set as the Azure SQL Microsoft Entra administrator. If Terraform cannot resolve that identity automatically, set
sql_aad_admin_nameandsql_aad_admin_object_idin your local*.auto.tfvarsfile. - If you plan to assign dashboard access to existing Entra groups, you need those groups' object IDs for
app_role_authorizations.
This repo includes environment wrappers under infra/terraform/environments. The checked-in terraform.tfvars files contain safe shared defaults such as the base app name and environment name. Put local-only overrides in an ignored *.auto.tfvars file.
If you want a brand-new environment wrapper, copy an existing one and then adjust its checked-in terraform.tfvars:
cp -R infra/terraform/environments/dev infra/terraform/environments/myenvThen update infra/terraform/environments/myenv/terraform.tfvars:
name = "app01"
environment = "myenv"If you only need the existing dev or tst wrappers, you can skip this copy step.
Choose the wrapper you want to deploy, then create a local file named <environment>.auto.tfvars in that directory. For example:
infra/terraform/environments/dev/dev.auto.tfvarsinfra/terraform/environments/tst/tst.auto.tfvars
Example infra/terraform/environments/dev/dev.auto.tfvars:
# Add the public IPv4 address of the machine running terraform apply so the
# sqlcmd helper can create contained database users and role grants.
sql_firewall_allowed_ipv4_addresses = ["203.0.113.10"]
# Optional if Terraform cannot resolve the current identity automatically.
# sql_aad_admin_name = "Ada Lovelace"
# sql_aad_admin_object_id = "00000000-0000-0000-0000-000000000000"
# Optional dashboard role assignments for existing Entra groups.
app_role_authorizations = {
dashboard_read = {
group_object_ids = ["00000000-0000-0000-0000-000000000001"]
}
dashboard_write = {
group_object_ids = ["00000000-0000-0000-0000-000000000002"]
}
}
# Optional Azure SQL database access for additional Microsoft Entra users,
# groups, managed identities, or service principals. The app database is keyed
# as "app"; additional databases must set name explicitly.
sql_database_access = {
app = {
principals = {
developers = {
name = "sg-app01-dev-sql-readers"
object_id = "00000000-0000-0000-0000-000000000003"
roles = ["db_datareader"]
}
}
}
reporting = {
name = "reporting-db"
principals = {
analysts = {
name = "sg-app01-reporting-readers"
object_id = "00000000-0000-0000-0000-000000000004"
roles = ["db_datareader"]
}
}
}
}
# Optional tags.
tags = {
environment = "dev"
}The repo ignores *.auto.tfvars, so keep environment-specific values there instead of committing them.
- Sign in to Azure CLI and select the target subscription.
az login
az account set --subscription "<subscription-id-or-name>"- Change into the environment wrapper or use Terraform's
-chdirflag.
terraform -chdir=infra/terraform/environments/dev init
terraform -chdir=infra/terraform/environments/dev applyWhat Terraform does:
- Provisions the resource group, App Service plan, Linux web app, Azure SQL server and database, Key Vault, and Microsoft Entra app registrations
- Configures Easy Auth on the web app
- Creates configured SQL contained database users and database role grants automatically through
sqlcmd - Writes a generated environment file at
infra/terraform/environments/<environment>/<environment>.env
By default, the deployment uses the free-oriented settings from this repository:
- App Service plan SKU
F1 - Azure SQL Database free-offer configuration
That generated .env file is used by:
After terraform apply, keep the generated .env file for later app deployments and API testing.
Use the app deployment flow when the Azure resources already exist and you only want to push a new version of the Flask app.
python3.12 -m venv .venv
.venv/bin/pip install -r requirements.txtThe deployment script uses the .env file generated by Terraform and deploys a ZIP package that contains app.py, requirements.txt, and templates/.
./scripts/deploy_app_only.sh ./infra/terraform/environments/dev/dev.envYou can also pass the file through ENV_FILE:
ENV_FILE=./infra/terraform/environments/dev/dev.env ./scripts/deploy_app_only.shRequirements for this step:
- Azure CLI must already be logged in
- The target web app must already exist
- The generated environment file must contain
RGandWEBAPP_NAME
Optional variables:
PACKAGE_PATHto override the ZIP output pathSKIP_BROWSE=trueto skip opening the site after deploymentAZURE_CONFIG_DIRif you want the script to use a non-default Azure CLI profile
.venv/bin/python -m unittest discover -s tests -vThe daemon test script uses the generated environment file from Terraform to request a client-credentials token and call GET /api/logins.
./scripts/test_daemon_api.sh ./infra/terraform/environments/dev/dev.envYou can also use:
ENV_FILE=./infra/terraform/environments/dev/dev.env ./scripts/test_daemon_api.shThe script expects the generated .env file to provide:
TENANT_IDCLIENT_IDSCOPETOKEN_URLAPI_URL
For the daemon secret, use one of these options:
- Preferred:
KEY_VAULT_NAMEandDAEMON_CLIENT_SECRET_NAME - Fallback:
CLIENT_SECRET
The script:
- Waits for
healthzto return HTTP 200 before calling the API - Uses Azure CLI to read the daemon secret from Key Vault when Key Vault variables are present
- Prints the decoded access token payload
- Calls the deployed
GET /api/loginsendpoint and prints the JSON response
If your app needs longer to warm up, override the health-check settings such as HEALTH_MAX_ATTEMPTS or HEALTH_RETRY_SECONDS before running the script.