Skip to content

Fail open and add redis - #7

Merged
Jeffreyhung merged 3 commits into
mainfrom
fail_open-and-add-redis
Jul 22, 2026
Merged

Fail open and add redis#7
Jeffreyhung merged 3 commits into
mainfrom
fail_open-and-add-redis

Conversation

@Jeffreyhung

Copy link
Copy Markdown
Member

to resolve the following:

we are seeing some Request failed "403 Forbidden" from our socket firewall starting yesterday, and can use some help on troubleshooting. Some more details:

We are running v2.0.4 of firewall, and it's heathy when the 403 happens https://sfw.security.sentry.io/health/
It's not user/device isolated, happened on both laptop and github actions
Here's an example: https://github.com/getsentry/sentry-conventions/actions/runs/29092310570/job/86360353960?pr=477

We first see this yesterday, and it resolved itself after 5-10 min without any action taken from our side

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Terraform plan in terraform

Plan: 9 to add, 2 to change, 0 to destroy. Changes to Outputs.
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+   create
!~  update in-place
 <= read (data resources)

Terraform will perform the following actions:

  # data.kubernetes_service.socket_firewall will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "kubernetes_service" "socket_firewall" {
+       id     = (known after apply)
+       spec   = (known after apply)
+       status = (known after apply)

+       metadata {
+           generation       = (known after apply)
+           name             = "socket-firewall"
+           namespace        = "socket-firewall"
+           resource_version = (known after apply)
+           uid              = (known after apply)
        }
    }

  # google_compute_firewall.allow_redis_egress will be created
+   resource "google_compute_firewall" "allow_redis_egress" {
+       creation_timestamp = (known after apply)
+       destination_ranges = (known after apply)
+       direction          = "EGRESS"
+       enable_logging     = (known after apply)
+       id                 = (known after apply)
+       name               = "socket-firewall-allow-redis-egress"
+       network            = "projects/sentry-socket/global/networks/socket-firewall-vpc"
+       priority           = 1000
+       project            = "sentry-socket"
+       self_link          = (known after apply)
+       target_tags        = [
+           "gke-socket-firewall",
        ]

+       allow {
+           ports    = [
+               "6378",
            ]
+           protocol = "tcp"
        }

+       log_config {
+           metadata = "INCLUDE_ALL_METADATA"
        }
    }

  # google_compute_global_address.redis_psa will be created
+   resource "google_compute_global_address" "redis_psa" {
+       address            = (known after apply)
+       address_type       = "INTERNAL"
+       creation_timestamp = (known after apply)
+       effective_labels   = {
+           "goog-terraform-provisioned" = "true"
        }
+       id                 = (known after apply)
+       label_fingerprint  = (known after apply)
+       name               = "socket-firewall-redis-psa"
+       network            = "projects/sentry-socket/global/networks/socket-firewall-vpc"
+       prefix_length      = 24
+       project            = "sentry-socket"
+       purpose            = "VPC_PEERING"
+       self_link          = (known after apply)
+       terraform_labels   = {
+           "goog-terraform-provisioned" = "true"
        }
    }

  # google_project_iam_custom_role.tf_plan_reader will be updated in-place
!~  resource "google_project_iam_custom_role" "tf_plan_reader" {
        id          = "projects/sentry-socket/roles/socketFirewallTfPlanReader"
        name        = "projects/sentry-socket/roles/socketFirewallTfPlanReader"
!~      permissions = [
+           "redis.instances.get",
+           "redis.instances.list",
+           "servicenetworking.services.get",
#            (42 unchanged elements hidden)
        ]
#        (6 unchanged attributes hidden)
    }

  # google_project_iam_member.tf_sa_redis_admin will be created
+   resource "google_project_iam_member" "tf_sa_redis_admin" {
+       etag    = (known after apply)
+       id      = (known after apply)
+       member  = "serviceAccount:socket-firewall-tf-apply@sac-prod-sa.iam.gserviceaccount.com"
+       project = "sentry-socket"
+       role    = "roles/redis.admin"
    }

  # google_project_iam_member.tf_sa_servicenetworking will be created
+   resource "google_project_iam_member" "tf_sa_servicenetworking" {
+       etag    = (known after apply)
+       id      = (known after apply)
+       member  = "serviceAccount:socket-firewall-tf-apply@sac-prod-sa.iam.gserviceaccount.com"
+       project = "sentry-socket"
+       role    = "roles/servicenetworking.networksAdmin"
    }

  # google_project_service.required["redis.googleapis.com"] will be created
+   resource "google_project_service" "required" {
+       disable_dependent_services = false
+       disable_on_destroy         = false
+       id                         = (known after apply)
+       project                    = "sentry-socket"
+       service                    = "redis.googleapis.com"
    }

  # google_redis_instance.verdict_cache will be created
+   resource "google_redis_instance" "verdict_cache" {
+       alternative_location_id     = (known after apply)
+       auth_enabled                = true
+       auth_string                 = (sensitive value)
+       authorized_network          = "projects/sentry-socket/global/networks/socket-firewall-vpc"
+       connect_mode                = "PRIVATE_SERVICE_ACCESS"
+       create_time                 = (known after apply)
+       current_location_id         = (known after apply)
+       display_name                = "Socket Firewall verdict cache"
+       effective_labels            = {
+           "app"                        = "socket-firewall"
+           "env"                        = "prod"
+           "goog-terraform-provisioned" = "true"
+           "team"                       = "team-security-2"
        }
+       effective_reserved_ip_range = (known after apply)
+       host                        = (known after apply)
+       id                          = (known after apply)
+       labels                      = {
+           "app"  = "socket-firewall"
+           "env"  = "prod"
+           "team" = "team-security-2"
        }
+       location_id                 = "*************"
+       maintenance_schedule        = (known after apply)
+       maintenance_version         = (known after apply)
+       memory_size_gb              = 1
+       name                        = "socket-firewall-verdict-cache"
+       nodes                       = (known after apply)
+       persistence_iam_identity    = (known after apply)
+       port                        = (known after apply)
+       project                     = "sentry-socket"
+       read_endpoint               = (known after apply)
+       read_endpoint_port          = (known after apply)
+       read_replicas_mode          = (known after apply)
+       redis_version               = "REDIS_7_0"
+       region                      = "us-central1"
+       replica_count               = (known after apply)
+       reserved_ip_range           = (known after apply)
+       secondary_ip_range          = (known after apply)
+       server_ca_certs             = (known after apply)
+       terraform_labels            = {
+           "app"                        = "socket-firewall"
+           "env"                        = "prod"
+           "goog-terraform-provisioned" = "true"
+           "team"                       = "team-security-2"
        }
+       tier                        = "BASIC"
+       transit_encryption_mode     = "SERVER_AUTHENTICATION"

+       persistence_config (known after apply)
    }

  # google_service_networking_connection.redis_psa will be created
+   resource "google_service_networking_connection" "redis_psa" {
+       id                      = (known after apply)
+       network                 = "projects/sentry-socket/global/networks/socket-firewall-vpc"
+       peering                 = (known after apply)
+       reserved_peering_ranges = [
+           "socket-firewall-redis-psa",
        ]
+       service                 = "servicenetworking.googleapis.com"
    }

  # helm_release.socket_firewall will be updated in-place
!~  resource "helm_release" "socket_firewall" {
        id                         = "socket-firewall"
!~      metadata                   = [
-           {
-               app_version    = "2.0.5"
-               chart          = "socket-firewall"
-               first_deployed = 1781303659
-               last_deployed  = 1784745855
-               name           = "socket-firewall"
-               namespace      = "socket-firewall"
-               notes          = <<-EOT
                    Socket Firewall deployed successfully!
                    
                    ## Self-Signed Certificates
                    
                    Self-signed certificates were generated. Extract the CA certificate:
                    
                      kubectl exec -n socket-firewall \
                        $(kubectl get pod -n socket-firewall -l app.kubernetes.io/name=socket-firewall -o jsonpath='{.items[0].metadata.name}') \
                        -- cat /etc/nginx/ssl/ca.crt > socket-ca.crt
                    
                    Then trust the CA on developer machines or use a real certificate for production.
                    
                    ## Accessing the Firewall
                    For testing, use port-forward:
                    
                      kubectl port-forward svc/socket-firewall 8443:443 -n socket-firewall
                    
                    For production, expose via Ingress or change service.type to LoadBalancer.
                    
                    ## Usage
                    
                    Path-based routing is enabled on domain: sfw.security.sentry.io
                    
                    Configure package managers to use path prefixes:
                      npm: https://sfw.security.sentry.io/npm/
                      pypi: https://sfw.security.sentry.io/pypi/
                      maven: https://sfw.security.sentry.io/maven/
                    
                    Example:
                      npm config set registry https://sfw.security.sentry.io/npm/
                      pip config set global.index-url https://sfw.security.sentry.io/pypi/simple
                    
                    ## Verify
                    
                    Test the health endpoint:
                    
                      curl -sk https://<firewall-ip>/health
                    
                    ## Metrics
                    
                    Prometheus metrics are exposed on port 9145 at /metrics (plain HTTP, no auth).
                    Set metrics.serviceMonitor.enabled=true (Prometheus Operator) or metrics.podAnnotations=true
                    (annotation-based) to scrape it. The endpoint is unauthenticated — restrict access with a NetworkPolicy.
                    
                    See deployment recommendations in README.md for production setup.
                EOT
-               revision       = 14
-               values         = jsonencode(
                    {
-                       affinity           = {
-                           podAntiAffinity = {
-                               preferredDuringSchedulingIgnoredDuringExecution = [
-                                   {
-                                       podAffinityTerm = {
-                                           labelSelector = {
-                                               matchLabels = {
-                                                   "app.kubernetes.io/instance" = "socket-firewall"
                                                }
                                            }
-                                           topologyKey   = "kubernetes.io/hostname"
                                        }
-                                       weight          = 100
                                    },
                                ]
                            }
                        }
-                       autoscaling        = {
-                           enabled = true
                        }
-                       image              = {
-                           pullPolicy = "IfNotPresent"
-                           repository = "socketdev/socket-registry-firewall"
-                           tag        = "2.0.6"
                        }
-                       initContainers     = {
-                           certGenerator = {
-                               securityContext = {
-                                   allowPrivilegeEscalation = false
-                                   capabilities             = {
-                                       drop = [
-                                           "ALL",
                                        ]
                                    }
-                                   readOnlyRootFilesystem   = true
-                                   runAsNonRoot             = true
-                                   runAsUser                = 1001
-                                   seccompProfile           = {
-                                       type = "RuntimeDefault"
                                    }
                                }
                            }
                        }
-                       pathRouting        = {
-                           domain  = "sfw.security.sentry.io"
-                           enabled = true
-                           routes  = [
-                               {
-                                   path     = "/npm"
-                                   registry = "npm"
-                                   upstream = "https://registry.npmjs.org"
                                },
-                               {
-                                   path     = "/pypi"
-                                   registry = "pypi"
-                                   upstream = "https://pypi.org"
                                },
-                               {
-                                   path     = "/maven"
-                                   registry = "maven"
-                                   upstream = "https://repo1.maven.org/maven2"
                                },
                            ]
                        }
-                       podSecurityContext = {
-                           fsGroup = 1001
                        }
-                       replicaCount       = 2
-                       resources          = {
-                           limits   = {
-                               cpu    = "1"
-                               memory = "768Mi"
                            }
-                           requests = {
-                               cpu    = "500m"
-                               memory = "512Mi"
                            }
                        }
-                       service            = {
-                           httpsTargetPort = "http"
-                           type            = "ClusterIP"
                        }
-                       socket             = {
-                           existingSecret    = "socket-api-token"
-                           existingSecretKey = "SOCKET_SECURITY_API_TOKEN"
-                           failOpen          = false
-                           failOpenUnscanned = false
                        }
-                       tls                = {
-                           generateSelfSigned = true
                        }
                    }
                )
-               version        = "0.9.0"
            },
        ] -> (known after apply)
        name                       = "socket-firewall"
!~      values                     = [
-           <<-EOT
                "affinity":
                  "podAntiAffinity":
                    "preferredDuringSchedulingIgnoredDuringExecution":
                    - "podAffinityTerm":
                        "labelSelector":
                          "matchLabels":
                            "app.kubernetes.io/instance": "socket-firewall"
                        "topologyKey": "kubernetes.io/hostname"
                      "weight": 100
                "autoscaling":
                  "enabled": true
                "image":
                  "pullPolicy": "IfNotPresent"
                  "repository": "socketdev/socket-registry-firewall"
                  "tag": "2.0.6"
                "initContainers":
                  "certGenerator":
                    "securityContext":
                      "allowPrivilegeEscalation": false
                      "capabilities":
                        "drop":
                        - "ALL"
                      "readOnlyRootFilesystem": true
                      "runAsNonRoot": true
                      "runAsUser": 1001
                      "seccompProfile":
                        "type": "RuntimeDefault"
                "pathRouting":
                  "domain": "sfw.security.sentry.io"
                  "enabled": true
                  "routes":
                  - "path": "/npm"
                    "registry": "npm"
                    "upstream": "https://registry.npmjs.org"
                  - "path": "/pypi"
                    "registry": "pypi"
                    "upstream": "https://pypi.org"
                  - "path": "/maven"
                    "registry": "maven"
                    "upstream": "https://repo1.maven.org/maven2"
                "podSecurityContext":
                  "fsGroup": 1001
                "replicaCount": 2
                "resources":
                  "limits":
                    "cpu": "1"
                    "memory": "768Mi"
                  "requests":
                    "cpu": "500m"
                    "memory": "512Mi"
                "service":
                  "httpsTargetPort": "http"
                  "type": "ClusterIP"
                "socket":
                  "existingSecret": "socket-api-token"
                  "existingSecretKey": "SOCKET_SECURITY_API_TOKEN"
                  "failOpen": false
                  "failOpenUnscanned": false
                "tls":
                  "generateSelfSigned": true
            EOT,
        ] -> (known after apply)
#        (26 unchanged attributes hidden)
    }

  # kubernetes_secret.redis_auth will be created
+   resource "kubernetes_secret" "redis_auth" {
+       binary_data_wo                 = (write-only attribute)
+       data                           = (sensitive value)
+       data_wo                        = (write-only attribute)
+       id                             = (known after apply)
+       type                           = "Opaque"
+       wait_for_service_account_token = true

+       metadata {
+           generation       = (known after apply)
+           name             = "socket-firewall-redis-auth"
+           namespace        = "socket-firewall"
+           resource_version = (known after apply)
+           uid              = (known after apply)
        }
    }

  # kubernetes_secret.redis_ca will be created
+   resource "kubernetes_secret" "redis_ca" {
+       binary_data_wo                 = (write-only attribute)
+       data                           = (sensitive value)
+       data_wo                        = (write-only attribute)
+       id                             = (known after apply)
+       type                           = "Opaque"
+       wait_for_service_account_token = true

+       metadata {
+           generation       = (known after apply)
+           name             = "socket-firewall-redis-ca"
+           namespace        = "socket-firewall"
+           resource_version = (known after apply)
+           uid              = (known after apply)
        }
    }

Plan: 9 to add, 2 to change, 0 to destroy.

Changes to Outputs:
+   redis_host                   = (known after apply)
+   redis_port                   = 6378

❌ Error applying plan in Terraform Apply #19

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9a90408. Configure here.

Comment thread terraform/redis.tf
depends_on = [
google_service_networking_connection.redis_psa,
google_project_service.required,
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redis apply races IAM bindings

Medium Severity

PSA and Memorystore resources do not depend on the new roles/redis.admin and roles/servicenetworking.networksAdmin bindings for the apply SA. On the first apply of this change those resources can be created before the bindings land, so creation fails with permission errors and blocks the Redis plus fail-open rollout.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9a90408. Configure here.

Comment thread terraform/redis.tf

data = {
"ca.crt" = google_redis_instance.verdict_cache.server_ca_certs[0].cert
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CA secret ignores rotation certs

Medium Severity

The Redis CA secret stores only server_ca_certs[0]. During Memorystore CA rotation Google exposes multiple CAs and clients must trust all of them; keeping a single cert can make TLS verification fail, so the shared verdict cache drops out while failOpen is enabled.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9a90408. Configure here.

@Jeffreyhung
Jeffreyhung merged commit b3f9d94 into main Jul 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant