You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Repo target:docker2azure4student Severity: High Owner: infra Type: security / hardening
Context
The Locus Azure infrastructure provisioned by docker2azure4student/main.tf exposes the
PostgreSQL Flexible Server to the public internet and broadens the firewall to all Azure
services. The Terraform state backend is also implicit/local, which means a working copy of
sensitive values (admin password, connection strings, storage keys) lives on whoever last
ran terraform apply. This issue tracks the changes needed to bring the database, the
firewall surface and the state management to a baseline that is acceptable for a
small-but-real production deployment.
Findings
1. CRITICAL — PostgreSQL Flexible Server is publicly reachable
Combined with the firewall rule below, the database accepts connections from the entire
public internet on port 5432. Any leaked credential becomes immediately exploitable, and
brute-force / credential-stuffing attempts hit the DB directly.
Proposal
Set public_network_access_enabled = false.
Provision a delegated subnet inside the existing VNet and switch the server to
VNet-integrated (delegated_subnet_id + private_dns_zone_id).
Have the VM reach PostgreSQL through the private endpoint over the VNet only.
In Azure semantics, a 0.0.0.0–0.0.0.0 rule on a Flexible Server allows any Azure
resource in any tenant to connect. Anyone with an Azure subscription can target the
server.
Proposal
Remove azure_services entirely.
Keep only vm_public_ip (already conditional on vm_public_ip_static) so that exactly
one IP can reach the database.
Document the requirement of vm_public_ip_static = true in variables.tf / terraform.tfvars.example.
3. MEDIUM — Terraform state has no remote backend
File: versions.tf / providers.tf
There is no backend "azurerm" { … } block, so state defaults to local. State contains
the DB admin password (administrator_password), storage account keys and any other
sensitive output. Local state means:
whoever ran apply has the secrets on disk in cleartext;
there is no locking, two concurrent applies corrupt the infra;
there is no audit trail.
Proposal
Create a dedicated storage account + container (e.g. tfstate-locus) outside of the
managed resource group, with:
min_tls_version = "TLS1_2",
allow_nested_items_to_be_public = false,
infrastructure_encryption_enabled = true,
RBAC restricted to the apply identity only (no shared keys),
The DB password is read from var.db_admin_password, typically supplied via terraform.tfvars. With remote state + RBAC this is acceptable for the student tier, but
the medium-term path is Azure AD authentication on the Flexible Server
(azurerm_postgresql_flexible_server_active_directory_administrator) so that the API VM
authenticates with a managed identity instead of a static password.
Acceptance criteria
azurerm_postgresql_flexible_server.db.public_network_access_enabled = false in the
committed code.
azurerm_postgresql_flexible_server_firewall_rule.azure_services is removed; only
the VM-scoped firewall rule remains (or no firewall rule at all if private endpoint
is in place).
Remote azurerm backend configured, state migrated, no local terraform.tfstate
tracked or shared.
README.md updated with the new connectivity model and migration steps.
plan / apply produces the expected diff in a staging subscription before being
applied to the live deployment.
Rollback plan
The PostgreSQL changes (public_network_access_enabled toggle and firewall rule
removal) can be reverted by re-applying the previous Terraform revision; existing
databases and data are unaffected.
Backend migration is one-way in practice: keep a copy of the local state before init -migrate-state so it can be re-imported into a different backend if Azure
storage is unavailable.
Related
Round 1 hardening (api/client/.github) is a separate effort; this issue is the infra
half.
See OIDC / GitHub App issue for the authentication side of the deploy pipeline.
Repo target:
docker2azure4studentSeverity: High
Owner: infra
Type: security / hardening
Context
The Locus Azure infrastructure provisioned by
docker2azure4student/main.tfexposes thePostgreSQL Flexible Server to the public internet and broadens the firewall to all Azure
services. The Terraform state backend is also implicit/local, which means a working copy of
sensitive values (admin password, connection strings, storage keys) lives on whoever last
ran
terraform apply. This issue tracks the changes needed to bring the database, thefirewall surface and the state management to a baseline that is acceptable for a
small-but-real production deployment.
Findings
1. CRITICAL — PostgreSQL Flexible Server is publicly reachable
File:
main.tf(resourceazurerm_postgresql_flexible_server.db)Combined with the firewall rule below, the database accepts connections from the entire
public internet on port 5432. Any leaked credential becomes immediately exploitable, and
brute-force / credential-stuffing attempts hit the DB directly.
Proposal
public_network_access_enabled = false.VNet-integrated (
delegated_subnet_id+private_dns_zone_id).network access disabled and add a single firewall rule scoped to the VM's static
public IP only (see Terraform / Azure infra hardening (PostgreSQL public access, firewall, state backend) #2).
2. MEDIUM — Firewall rule
allow-azure-servicesis effectively0.0.0.0/0for Azure tenantsFile:
main.tf(resourceazurerm_postgresql_flexible_server_firewall_rule.azure_services)In Azure semantics, a
0.0.0.0–0.0.0.0rule on a Flexible Server allows any Azureresource in any tenant to connect. Anyone with an Azure subscription can target the
server.
Proposal
azure_servicesentirely.vm_public_ip(already conditional onvm_public_ip_static) so that exactlyone IP can reach the database.
vm_public_ip_static = trueinvariables.tf/terraform.tfvars.example.3. MEDIUM — Terraform state has no remote backend
File:
versions.tf/providers.tfThere is no
backend "azurerm" { … }block, so state defaults to local. State containsthe DB admin password (
administrator_password), storage account keys and any othersensitive output. Local state means:
Proposal
tfstate-locus) outside of themanaged resource group, with:
min_tls_version = "TLS1_2",allow_nested_items_to_be_public = false,infrastructure_encryption_enabled = true,terraform init -migrate-state.terraform.tfstate*(already gitignored — verify).4. LOW — Admin password lives in
tfvarsThe DB password is read from
var.db_admin_password, typically supplied viaterraform.tfvars. With remote state + RBAC this is acceptable for the student tier, butthe medium-term path is Azure AD authentication on the Flexible Server
(
azurerm_postgresql_flexible_server_active_directory_administrator) so that the API VMauthenticates with a managed identity instead of a static password.
Acceptance criteria
azurerm_postgresql_flexible_server.db.public_network_access_enabled = falsein thecommitted code.
azurerm_postgresql_flexible_server_firewall_rule.azure_servicesis removed; onlythe VM-scoped firewall rule remains (or no firewall rule at all if private endpoint
is in place).
azurermbackend configured, state migrated, no localterraform.tfstatetracked or shared.
README.mdupdated with the new connectivity model and migration steps.plan/applyproduces the expected diff in a staging subscription before beingapplied to the live deployment.
Rollback plan
public_network_access_enabledtoggle and firewall ruleremoval) can be reverted by re-applying the previous Terraform revision; existing
databases and data are unaffected.
init -migrate-stateso it can be re-imported into a different backend if Azurestorage is unavailable.
Related
half.