Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Deployment templates for the Socket Registry Firewall.

- `helm/` — Kubernetes Helm chart (migrated from `socketdev-demo/socket-firewall-helm` with full commit history).
- `cloudformation/` — AWS CloudFormation templates (in progress).
- `terraform/` — Terraform templates.
- `terraform/azure-container-apps/` — Azure Container Apps (migrated from
`socketdev-demo/socket-firewall-azure-container-apps` with full commit history).

## Helm chart publishing

Expand Down
7 changes: 7 additions & 0 deletions terraform/azure-container-apps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl
tfplan*
terraform.tfvars
.DS_Store
175 changes: 175 additions & 0 deletions terraform/azure-container-apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Socket Firewall - Azure Container Apps (Terraform)

Terraform template for deploying the [Socket Registry Firewall](https://github.com/SocketDev/socket-nginx-firewall) on Azure Container Apps.

## What it provisions

- Resource Group
- Container Apps Environment with VNet integration and internal load balancer
- Container App with health probes and CPU-based scaling
- Key Vault for credentials (API token, SSL cert/key)
- User-assigned Managed Identity (for Key Vault access)
- Log Analytics workspace

## Prerequisites

- Terraform >= 1.5
- Azure CLI configured (`az login`)
- A VNet with a subnet delegated to Container Apps (minimum /23 CIDR)
- A Socket.dev API token ([create one here](https://socket.dev/dashboard/org/settings/api-tokens)) with `packages` and `entitlements:list` scopes
- SSL certificate and private key files

## Usage

```bash
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values

terraform init
terraform plan
terraform apply
```

## Inputs

See `variables.tf` for all configurable inputs with descriptions and defaults.

Key variables:
- `socket_api_token` - Socket.dev API token (required, sensitive)
- `domain` - Hostname clients use to reach the firewall (required). Set to the FQDN from terraform output or your custom DNS name.
- `registries` - Map of registry name to upstream URL (default: npm only)
- `ssl_cert` / `ssl_key` - SSL certificate PEM content (required, sensitive)
- `subnet_id` / `vnet_id` - Network configuration (required)
- `min_replicas` / `max_replicas` - Scaling bounds (default: 1 / 5)
- `cpu` / `memory` - Container resources (default: 1.0 / 2Gi)
- `debug_logging_enabled` - Enable debug logging for HTTP requests/responses (default: false)
- `debug_user_agent_filter` - Glob pattern to filter debug logs by user-agent (default: "")
- `recently_published_enabled_ecosystems` - Ecosystems to enforce recently-published blocking (default: [])
- `redis_enabled` / `redis_host` / `redis_port` / `redis_password` / `redis_ssl` - Optional shared cache across replicas (default: disabled). For Azure Cache for Redis: use cluster mode disabled (Basic/Standard, or Premium with clustering off), port 6380, `redis_ssl = true`, and set `redis_password` to an access key. Redis failures fail safe to each replica's local cache.
- `socket_api_token_key_vault_secret_id` / `ssl_cert_key_vault_secret_id` / `ssl_key_key_vault_secret_id` / `redis_password_key_vault_secret_id` - Reference existing Key Vault secrets created out-of-band instead of passing values through Terraform. Secret values passed directly (`socket_api_token`, `ssl_cert`/`ssl_key`, `redis_password`) are persisted in Terraform state even though they are marked sensitive; the `*_key_vault_secret_id` variants keep them out of Terraform entirely. The Container App managed identity needs the Key Vault Secrets User role on the vault holding referenced secrets. Note: with `generate_self_signed_cert = true` (the default), the private key is generated by Terraform and stored in state regardless; bring your own certificate via the KV references if state must stay free of key material. Either way, protect the state file itself (remote backend with access control).

## Registries

The `registries` variable controls path-based routing. Each entry creates a route at `/<name>` that proxies to the upstream URL.

### Direct routes (firewall in front of public registries)

```hcl
registries = {
npm = "https://registry.npmjs.org"
pypi = "https://pypi.org"
maven = "https://repo1.maven.org/maven2"
}
```

```bash
npm config set registry https://registry.company.com/npm
pip install --index-url https://registry.company.com/pypi/simple <package>
```

### Upstream mode (firewall in front of Artifactory)

If you use Artifactory (or another artifact repository manager), use `/repository/<repo-name>` paths to match Artifactory's URL convention:

```hcl
registries = {
"repository/npm-remote" = "https://company.jfrog.io/artifactory/api/npm/npm-remote"
"repository/pypi-remote" = "https://company.jfrog.io/artifactory/api/pypi/pypi-remote"
}

# Required whenever a route name is not itself an ecosystem name.
# Without these, the firewall does not recognize "repository/npm-remote" as an
# ecosystem and serves the route through a plain streaming proxy, forwarding
# packages with no inspection. Installs succeed and the deployment looks
# healthy; the only signal is that no SOCKET_DECISION lines appear in the logs.
registry_overrides = {
"repository/npm-remote" = "npm"
"repository/pypi-remote" = "pypi"
}
```

```bash
npm config set registry https://registry.company.com/repository/npm-remote
pip install --index-url https://registry.company.com/repository/pypi-remote/simple <package>
```

## Outputs

- `fqdn` - Internal FQDN of the Container App
- `resource_group_name` - Resource group name
- `container_app_name` - Container App name
- `container_app_environment_name` - Container Apps Environment name
- `key_vault_name` - Key Vault name
- `managed_identity_client_id` - Managed Identity client ID

## Verify the deployment

The firewall runs on an internal load balancer, so test from a VM or resource within the VNet.

```bash
# Health check — should include "path-routing" in the response
curl -k https://<FQDN>/health
# Expected: SocketFirewall/x.x.x - Health OK - path-routing (...)

# Test a safe package
npm install lodash --registry https://<FQDN>/npm

# Test a blocked package
# IMPORTANT: clear npm cache first, or cached tarballs bypass the firewall
npm cache clean --force
npm install peacenotwar@9.1.3 --registry https://<FQDN>/npm --prefer-online
# Expected: E403 "Blocked by Security Policy"
```

## Troubleshooting

After `terraform apply`, run `terraform output troubleshooting` to see useful debugging commands for your deployment.

To enable verbose logging, set `log_level = "debug"` in your tfvars and redeploy. This sets nginx error_log to debug level, showing TLS handshake details, upstream connections, and request routing decisions.

**Containers keep restarting**
Check logs in Log Analytics or with `az containerapp logs show`. The most common cause is an invalid or missing SSL certificate. If using the default self-signed cert (`generate_self_signed_cert = true`), verify the SANs with `terraform output ssl_cert_sans`.

**404 errors on package requests**
The `domain` variable must match the Host header that clients send. If you are using the Container App FQDN directly, set `domain` to that FQDN. Run `terraform output fqdn` to get the value.

**Azure Front Door: 421 SSLMismatchedSNI**
Front Door validates that the Host header matches a custom domain configured on the Front Door profile. This error means either:
1. The custom domain is not associated with the Front Door endpoint/route, or
2. The origin host header does not match the cert's SANs.

The `domain` variable controls the cert SANs (when using the self-signed cert). Include all hostnames that Front Door might send, separated by spaces:
```hcl
domain = "registry.company.com ca-socket-fw.xxxxx.eastus.azurecontainerapps.io"
```

**Tarball URLs point to the Container App FQDN instead of the customer-facing domain**
The firewall rewrites tarball URLs using the `Host` header it receives. If Front Door's origin host header is set to the Container App FQDN, tarball URLs will use that FQDN, and npm clients will try to download tarballs directly (bypassing Front Door), which fails with ECONNRESET.

Fix: Set the Front Door origin host header to the customer-facing domain (e.g., `registry.company.com`), and make sure that domain is included in the `domain` variable so the cert's SANs match.

**Packages install but are not scanned**
Verify your API token has the `packages` and `entitlements:list` scopes. With `socket_fail_open = true` (the default), invalid or missing tokens silently pass all packages through without scanning. Check container logs for `Firewall access validation failed` or `401` errors.

**Secret changes not taking effect after terraform apply**
Container Apps secret volumes are immutable per revision. Restarting the same revision reloads the same secrets. Force a new revision:
```bash
az containerapp update -n <app-name> -g <resource-group>
```

**Key Vault soft-delete conflict**
If you destroy and recreate the stack, Azure retains the Key Vault in a soft-deleted state for 7 days. Either purge it manually (`az keyvault purge --name <vault-name>`) or use a different `environment_name`.

## Notes

**Custom domain binding**: When `generate_self_signed_cert = true`, the template automatically registers each hostname in the `domain` variable as a custom domain on the Container App. This is required so the Container Apps ingress accepts requests with those Host headers. Without it, requests from Front Door (or any client using a custom hostname) get a 404 from the ingress layer before reaching nginx.

Azure Container Apps mounts secrets as files in a shared volume at `/mnt/config/`. The template sets the `CONFIG_FILE` env var so the firewall reads socket.yml from the correct path. SSL certificate paths in the generated `socket.yml` reference `/mnt/config/ssl-cert` and `/mnt/config/ssl-key`.

The `socket.yml` config is auto-generated from the `registries` and `domain` variables. You do not need to write or encode it manually.

## Other deployment options

- **Already on Kubernetes?** Use the [Helm chart](https://github.com/socketdev-demo/socket-firewall-helm)
- **On AWS?** See [socket-firewall-aws-ecs-fargate](https://github.com/socketdev-demo/socket-firewall-aws-ecs-fargate)
- **On GCP?** See [socket-firewall-gcp-cloud-run](https://github.com/socketdev-demo/socket-firewall-gcp-cloud-run)
Loading