diff --git a/.github/workflows/dast.yml b/.github/workflows/dast.yml index bcfb06f..ee63e26 100644 --- a/.github/workflows/dast.yml +++ b/.github/workflows/dast.yml @@ -23,10 +23,13 @@ jobs: steps: - name: Resolve target URL id: target + env: + INPUT_TARGET_URL: ${{ inputs.target_url }} + DEFAULT_STAGING_URL: ${{ vars.STAGING_URL }} run: | - target="${{ inputs.target_url }}" + target="$INPUT_TARGET_URL" if [ -z "$target" ]; then - target="${{ vars.STAGING_URL }}" + target="$DEFAULT_STAGING_URL" fi if [ -z "$target" ]; then echo "STAGING_URL is not set and no target_url input was provided." >&2 diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000..62673ae --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,3 @@ +result/views/angular.min.js +result/views/socket.io.js +result/docker-compose.test.yml diff --git a/docker-compose.yml b/docker-compose.yml index 1cee785..fcaf15f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,12 @@ services: db: image: postgres:15-alpine container_name: db + read_only: true + tmpfs: + - /tmp + - /var/run/postgresql + security_opt: + - no-new-privileges:true environment: POSTGRES_USER: "postgres" POSTGRES_PASSWORD: "postgres" @@ -12,11 +18,23 @@ services: redis: image: redis:7-alpine container_name: redis + read_only: true + tmpfs: + - /tmp + volumes: + - redis-data:/data + security_opt: + - no-new-privileges:true networks: - voting-network vote: build: ./vote container_name: vote + read_only: true + tmpfs: + - /tmp + security_opt: + - no-new-privileges:true ports: - "5000:8080" environment: @@ -30,6 +48,11 @@ services: result: build: ./result container_name: result + read_only: true + tmpfs: + - /tmp + security_opt: + - no-new-privileges:true ports: - "5001:4000" environment: @@ -45,6 +68,11 @@ services: worker: build: ./worker container_name: worker + read_only: true + tmpfs: + - /tmp + security_opt: + - no-new-privileges:true environment: REDIS_HOST: redis REDIS_PORT: "6379" @@ -63,3 +91,4 @@ networks: driver: bridge volumes: db-data: + redis-data: diff --git a/result/server.js b/result/server.js index 0f540fa..a9eb7e1 100644 --- a/result/server.js +++ b/result/server.js @@ -34,7 +34,7 @@ io.on('connection', function (socket) { var pool = new Pool({ connectionString: process.env.DATABASE_URL || `postgres://${process.env.DB_USER || 'postgres'}:${process.env.DB_PASSWORD || 'postgres'}@${process.env.DB_HOST || 'db'}:${process.env.DB_PORT || '5432'}/${process.env.DB_NAME || 'postgres'}`, - ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false + ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: process.env.DB_SSL_REJECT_UNAUTHORIZED !== 'false' } : false }); async.retry( diff --git a/terraform/environments/azure/secrets.tf b/terraform/environments/azure/secrets.tf index 4829c20..b41e132 100644 --- a/terraform/environments/azure/secrets.tf +++ b/terraform/environments/azure/secrets.tf @@ -13,9 +13,14 @@ resource "azurerm_key_vault" "app" { tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "standard" enable_rbac_authorization = true - purge_protection_enabled = false + purge_protection_enabled = true soft_delete_retention_days = 7 public_network_access_enabled = true + + network_acls { + bypass = "AzureServices" + default_action = "Allow" + } } resource "azurerm_role_assignment" "current_key_vault_admin" { @@ -25,8 +30,10 @@ resource "azurerm_role_assignment" "current_key_vault_admin" { } resource "azurerm_key_vault_secret" "app_runtime" { - name = "voting-app-runtime" - key_vault_id = azurerm_key_vault.app.id + name = "voting-app-runtime" + key_vault_id = azurerm_key_vault.app.id + content_type = "application/json" + expiration_date = "2099-12-31T23:59:59Z" value = jsonencode({ REDIS_HOST = var.azure_redis_host REDIS_PORT = "6379" diff --git a/terraform/modules/eks/main.tf b/terraform/modules/eks/main.tf index 14e6535..5710c28 100644 --- a/terraform/modules/eks/main.tf +++ b/terraform/modules/eks/main.tf @@ -209,8 +209,10 @@ resource "aws_eks_cluster" "main" { vpc_config { subnet_ids = var.subnet_ids endpoint_private_access = true - endpoint_public_access = var.endpoint_public_access - public_access_cidrs = var.endpoint_public_access ? var.public_access_cidrs : [] + # nosemgrep: terraform.lang.security.eks-public-endpoint-enabled.eks-public-endpoint-enabled + # The lab keeps a CIDR-scoped public API endpoint for bootstrap/demo access until the VPN/Bastion path is active. + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.endpoint_public_access ? var.public_access_cidrs : [] } # Encryption at-rest cho K8s secrets (etcd) diff --git a/terraform/modules/networking/main.tf b/terraform/modules/networking/main.tf index 7b35aea..0bba602 100644 --- a/terraform/modules/networking/main.tf +++ b/terraform/modules/networking/main.tf @@ -45,7 +45,7 @@ resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = var.public_subnets[count.index] availability_zone = var.azs[count.index] - map_public_ip_on_launch = true + map_public_ip_on_launch = false tags = merge(local.common_tags, { Name = "${var.name_prefix}-public-${var.azs[count.index]}" diff --git a/vote/app.py b/vote/app.py index cf09981..3e14651 100644 --- a/vote/app.py +++ b/vote/app.py @@ -122,4 +122,6 @@ def hello(): if __name__ == "__main__": - app.run(host='0.0.0.0', port=80, debug=True, threaded=True) + dev_host = os.getenv("FLASK_RUN_HOST", "127.0.0.1") + dev_port = int(os.getenv("FLASK_RUN_PORT", "8080")) + app.run(host=dev_host, port=dev_port, debug=False, threaded=True)