Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 12 additions & 13 deletions .github/workflows/check-firewall-versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,30 @@ jobs:
shell: bash
run: |
set -euo pipefail
TF=terraform/variables.tf
TF=terraform/terraform.tfvars

read_default() {
read_tfvar() {
local val
val=$(sed -n "/variable \"$1\"/,/}/p" "$TF" \
| grep -E '^\s+default\s*=' | head -1 \
| sed -E 's/.*"([^"]+)".*/\1/' || true)
val=$(grep -E "^[[:space:]]*$1[[:space:]]*=" "$TF" \
| head -1 \
| sed -E 's/.*=[[:space:]]*"([^"]+)".*/\1/' || true)
if [ -z "$val" ]; then
echo "::error::variable \"$1\" not found or has no default in $TF" >&2
echo "::error::$1 not found in $TF" >&2
exit 1
fi
echo "$val"
}

set_default() {
sed -i "/variable \"$1\"/,/}/ s/default *= *\"[^\"]*\"/default = \"$2\"/" "$TF"
set_tfvar() {
sed -i -E "s/^([[:space:]]*$1[[:space:]]*=[[:space:]]*)\"[^\"]*\"/\1\"$2\"/" "$TF"
}

newer_than() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | tail -1)" = "$2" ] && [ "$1" != "$2" ]
}

current_chart=$(read_default helm_chart_version)
current_image=$(read_default firewall_image_tag)
current_chart=$(read_tfvar helm_chart_version)
current_image=$(read_tfvar firewall_image_tag)

latest_chart=$(curl -fsSL https://socketdev-demo.github.io/socket-firewall-helm/index.yaml \
| grep -E '^\s+version:' | awk '{print $2}' | sort -V | tail -1)
Expand All @@ -71,11 +71,11 @@ jobs:

summary=""
if newer_than "$current_chart" "$latest_chart"; then
set_default helm_chart_version "$latest_chart"
set_tfvar helm_chart_version "$latest_chart"
summary+="- \`helm_chart_version\`: \`$current_chart\` → \`$latest_chart\`"$'\n'
fi
if newer_than "$current_image" "$latest_image"; then
set_default firewall_image_tag "$latest_image"
set_tfvar firewall_image_tag "$latest_image"
summary+="- \`firewall_image_tag\`: \`$current_image\` → \`$latest_image\`"$'\n'
fi

Expand Down Expand Up @@ -106,7 +106,6 @@ jobs:

## Follow-up
- Review the `terraform plan` check on this PR.
- If you override versions in `terraform/terraform.tfvars`, update those values to match before merging.
- Confirm the new image is allowed by Binary Authorization before merging to `main`.

Triggered by [check-firewall-versions](.github/workflows/check-firewall-versions.yaml).
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/tf-apply.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ jobs:
${{ steps.auth.outcome == 'success'}}
with:
auto_approve: true
path: terraform
path: terraform
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 3 additions & 1 deletion .github/workflows/tf-plan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ jobs:
${{ steps.auth.outcome == 'success'}}
with:
add_github_comment: changes-only
path: terraform
path: terraform
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ Roles Terraform grants to the **apply SA**:
| `roles/iam.serviceAccountUser` | Attach the node SA to node-pool VMs |
| `roles/gkehub.gatewayEditor` + `roles/gkehub.viewer` | Reach the control plane via Connect Gateway; refresh the fleet membership |

The **plan SA** receives read-only equivalents: custom `socketFirewallTfPlanReader`, `roles/container.viewer`, `roles/certificatemanager.viewer`, and `roles/gkehub.gatewayReader`.
The **plan SA** receives read-only equivalents: custom `socketFirewallTfPlanReader`, `roles/container.viewer`, `roles/certificatemanager.viewer`, and `roles/gkehub.gatewayReader`. Because `container.viewer` maps to the `view` ClusterRole (which excludes secrets), a namespace-scoped Kubernetes `Role`/`RoleBinding` in [`kubernetes_rbac.tf`](terraform/kubernetes_rbac.tf) also grants `secrets` get/list/watch in `socket-firewall` for `terraform plan`.

Because Terraform creates these custom roles and bindings on the first apply, the bootstrap identity (a project admin) must first grant the apply SA enough elevated access to perform that initial run, then tighten it:

Expand Down
3 changes: 2 additions & 1 deletion terraform/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ resource "google_project_iam_member" "tf_plan_sa_reader" {
}

# Kubernetes API read access for the helm/kubernetes/kubectl providers during plan.
# roles/container.viewer maps to the "view" RBAC ClusterRole in GKE.
# roles/container.viewer maps to the "view" RBAC ClusterRole in GKE, which omits
# secrets — see kubernetes_rbac.tf for the namespace-scoped secret read binding.
resource "google_project_iam_member" "tf_plan_sa_k8s_viewer" {
project = var.project_id
role = "roles/container.viewer"
Expand Down
40 changes: 40 additions & 0 deletions terraform/kubernetes_rbac.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ---------------------------------------------------------------------------
# Supplemental Kubernetes RBAC for the plan SA
# ---------------------------------------------------------------------------
# roles/container.viewer maps to the predefined "view" ClusterRole, which
# deliberately omits secrets. Terraform plan still refreshes
# kubernetes_secret.socket_api_token (helm.tf), which calls the Kubernetes API
# secrets.get endpoint. Grant read-only secret access in this namespace only.
# ---------------------------------------------------------------------------

resource "kubernetes_role" "tf_plan_secret_reader" {
metadata {
name = "terraform-plan-secret-reader"
namespace = kubernetes_namespace.socket_firewall.metadata[0].name
}

rule {
api_groups = [""]
resources = ["secrets"]
verbs = ["get", "list", "watch"]
}
}

resource "kubernetes_role_binding" "tf_plan_secret_reader" {
metadata {
name = "terraform-plan-secret-reader"
namespace = kubernetes_namespace.socket_firewall.metadata[0].name
}

role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = kubernetes_role.tf_plan_secret_reader.metadata[0].name
}

subject {
kind = "User"
name = var.terraformer_plan
api_group = "rbac.authorization.k8s.io"
}
}
4 changes: 2 additions & 2 deletions terraform/terraform.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ node_max_count = 3
firewall_domain = "sfw.security.sentry.io."

replica_count = 2
helm_chart_version = "0.2.4"
firewall_image_tag = "1.1.159"
helm_chart_version = "0.3.0"
firewall_image_tag = "1.1.327"
Comment thread
Jeffreyhung marked this conversation as resolved.
2 changes: 0 additions & 2 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,11 @@ variable "firewall_domain" {
variable "helm_chart_version" {
description = "Version of the socket-firewall Helm chart"
type = string
default = "0.2.4"
}

variable "firewall_image_tag" {
description = "Container image tag for socketdev/socket-registry-firewall (pinned for reproducible rollouts; bumped by check-firewall-versions workflow)"
type = string
default = "1.1.159"
}

variable "replica_count" {
Expand Down
Loading