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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
This repository provides comprehensive infrastructure-as-code templates for integrating Nullify's security platform with your cloud environment. It includes multiple deployment options to suit different infrastructure preferences and requirements.

### **What's Included**
- ⚙️ **Helm Charts** - Production-ready Kubernetes deployment with IRSA support
- ⚙️ **Helm Charts** - Production-ready Kubernetes deployment for **EKS (IRSA)** and **GKE (Workload Identity)**
- 🏗️ **CloudFormation Templates** - AWS infrastructure setup with IAM roles and policies
- 🔧 **Terraform Modules** - Modular infrastructure-as-code for AWS and multi-cluster EKS integration
- ☁️ **GCP Terraform** - Read-only GCP integration via Workload Identity Federation
- 🤖 **GitHub Actions** - Automated chart publishing and validation
- 📚 **Documentation** - Comprehensive setup and security guides
- ❌ **NO real sensitive data, bucket names, or account IDs**
Expand All @@ -28,17 +29,19 @@ This repository provides comprehensive infrastructure-as-code templates for inte
2. **CloudFormation** (`aws-integration-setup/cloudformation/`) - For AWS-centric infrastructure
3. **Terraform** (`aws-integration-setup/terraform/`) - For infrastructure-as-code workflows with modular architecture
4. **GCP Terraform** (`gcp-integration-setup/terraform/`) - For Google Cloud read-only integration via Workload Identity Federation
5. **GKE Collector Terraform** (`gcp-integration-setup/terraform/modules/nullify-gke-collector/`) - For deploying the k8s-collector on GKE clusters

## 🚀 **Quick Start**

### **Choose Your Deployment Method**

| Method | Best For | Prerequisites |
|--------|----------|---------------|
| **🎯 Helm Charts** | Kubernetes-native teams, GitOps workflows | EKS cluster, Helm 3.x, kubectl |
| **🎯 Helm Charts** | Kubernetes-native teams, GitOps workflows | EKS or GKE cluster, Helm 3.x, kubectl |
| **🏗️ CloudFormation** | AWS-centric infrastructure, ClickOps teams | AWS CLI, appropriate IAM permissions |
| **🔧 Terraform (AWS)** | Infrastructure-as-code, multi-cluster teams | Terraform, AWS provider configured |
| **☁️ Terraform (GCP)** | GCP environments, Workload Identity Federation | Terraform, `gcloud` auth on the host project, org or folder admin access |
| **☁️ Terraform (GKE collector)** | GKE clusters running the Nullify k8s-collector | Terraform, GKE with Workload Identity enabled |

### **GCP Quick Start**

Expand All @@ -59,7 +62,7 @@ See [`gcp-integration-setup/terraform/README.md`](gcp-integration-setup/terrafor

1. **AWS Account** with appropriate permissions
2. **Nullify Account** and dashboard access
3. **EKS Cluster** (for Kubernetes deployments)
3. **Kubernetes cluster** for Helm deployments — **EKS** (IRSA) or **GKE** (Workload Identity). See the [chart README](helm-charts/nullify-k8s-collector/README.md#supported-platforms) for per-platform onboarding steps.

**Obtain Configuration Values from Nullify Configure Page:**

Expand Down Expand Up @@ -319,7 +322,7 @@ nullify-cloud-connector/

| Component | Purpose | Use When |
|-----------|---------|----------|
| **🎯 Helm Charts** | Deploy K8s collector with IRSA | You have EKS and prefer K8s-native tools |
| **🎯 Helm Charts** | Deploy K8s collector (EKS via IRSA, GKE via Workload Identity) | You have EKS or GKE and prefer K8s-native tools |
| **🏗️ CloudFormation** | Set up AWS IAM roles and policies | You prefer AWS-native infrastructure |
| **🔧 Terraform** | Modular infrastructure-as-code with multi-cluster support | You use Terraform for infrastructure |
| **🤖 GitHub Actions** | Automated testing and publishing | You want CI/CD for chart updates |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Nullify GKE Collector — Terraform Module

Creates the GCP-side resources needed to deploy the [Nullify Kubernetes collector](../../../helm-charts/nullify-k8s-collector/) on a GKE cluster.

## What this module creates

| Resource | Purpose |
|---|---|
| `google_service_account` | OIDC identity anchor — the collector pod impersonates this SA via Workload Identity. **No GCP IAM roles are needed**; the SA only signs tokens that AWS STS validates. |
| `google_service_account_iam_member` | Workload Identity binding (`roles/iam.workloadIdentityUser`) that lets the in-cluster Kubernetes ServiceAccount impersonate the GCP SA. |

## Prerequisites

- **GKE Workload Identity** must be enabled on the cluster (`--workload-pool=PROJECT.svc.id.goog`). This module does not enable it — it's a cluster-level setting.
- **Terraform** >= 1.3 with the `google` provider >= 4.0.

## Usage

```hcl
module "nullify_gke_collector" {
source = "./modules/nullify-gke-collector"
project_id = "my-gcp-project"
}
```

After `terraform apply`:

1. Share the `service_account_unique_id` output with Nullify. Nullify adds it to the federated IAM role's trust-policy allowlist and returns the AWS role ARN.
2. Deploy the Helm chart:

```yaml
# values-gke.yaml
cloudProvider: gcp

collector:
clusterName: "my-gke-cluster"
aws:
region: "us-east-1" # Region of the Nullify S3 bucket
s3:
bucket: "your-nullify-bucket" # Provided by Nullify
kms:
keyArn: "arn:aws:kms:..." # Provided by Nullify
gke:
nullifyAwsRoleArn: "arn:aws:iam::123456789012:role/..." # Provided by Nullify

serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "<service_account_email output>"
```

```bash
helm install nullify-k8s-collector ./nullify-k8s-collector -f values-gke.yaml
```

## Inputs

| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| `project_id` | GCP project where the GKE cluster runs | `string` | — | yes |
| `service_account_name` | Name of the GCP SA to create | `string` | `nullify-k8s-collector` | no |
| `k8s_namespace` | Kubernetes namespace (must match Helm `serviceAccount.namespace`) | `string` | `nullify` | no |
| `k8s_service_account_name` | Kubernetes SA name (must match Helm `serviceAccount.name`) | `string` | `nullify-k8s-collector-sa` | no |

## Outputs

| Name | Description |
|---|---|
| `service_account_email` | GCP SA email — use as the `iam.gke.io/gcp-service-account` annotation in Helm values |
| `service_account_unique_id` | 21-digit SA unique ID — share with Nullify for the AWS trust-policy allowlist |
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Nullify GKE Collector — customer-side GCP resources
#
# This module creates the minimal GCP resources a customer needs so the
# Nullify k8s-collector running inside a GKE cluster can authenticate to
# Nullify's AWS backend via Workload Identity Federation.
#
# The authentication flow:
#
# 1. GKE Workload Identity projects a Google-signed ServiceAccount ID
# token into the collector pod (audience: sts.amazonaws.com).
# 2. The collector calls AWS sts:AssumeRoleWithWebIdentity, presenting
# that Google-signed token plus the Nullify-owned federated IAM role ARN.
# 3. AWS validates the token against the accounts.google.com OIDC provider
# and checks that the token's `sub` claim (the GCP SA unique ID) is in
# the federated role's trust-policy allowlist.
# 4. AWS returns short-lived credentials scoped to s3:PutObject on the
# k8s-collector/ prefix — the collector uploads cluster metadata and
# exits.
#
# No long-lived AWS credential is ever stored in the customer cluster, and
# the GCP service account this module creates does NOT need any GCP IAM
# roles — it is purely an OIDC identity anchor whose signed token AWS STS
# trusts.
#
# Prerequisites:
# - GKE Workload Identity must be enabled on the cluster
# (--workload-pool=PROJECT.svc.id.goog). This module does NOT enable
# it; it is a cluster-level setting the customer manages.
#
# After applying:
# 1. Share the `service_account_unique_id` output with Nullify. Nullify
# adds it to the federated IAM role's trust-policy allowlist and
# returns the role ARN.
# 2. Deploy the nullify-k8s-collector Helm chart with cloudProvider=gcp
# and the values shown in the outputs.

# ---------------------------------------------------------------------------
# Service account — the OIDC identity the collector pod assumes via
# Workload Identity. No GCP IAM roles are attached; its only purpose is
# to sign an ID token that AWS STS will validate.
# ---------------------------------------------------------------------------

resource "google_service_account" "collector" {
project = var.project_id
account_id = var.service_account_name
display_name = "Nullify Kubernetes Collector"
description = "OIDC identity anchor for the Nullify k8s-collector. Bound to the in-cluster Kubernetes ServiceAccount via Workload Identity. Managed by Terraform."
}

# ---------------------------------------------------------------------------
# Workload Identity binding — allows the Kubernetes ServiceAccount
# (running inside the GKE cluster) to impersonate the GCP service account.
#
# The `member` format is:
# serviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA_NAME]
#
# where NAMESPACE and KSA_NAME must match the Helm chart's
# serviceAccount.namespace and serviceAccount.name values.
# ---------------------------------------------------------------------------

resource "google_service_account_iam_member" "workload_identity_binding" {
service_account_id = google_service_account.collector.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[${var.k8s_namespace}/${var.k8s_service_account_name}]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "service_account_email" {
description = "Email of the GCP service account. Use this as the iam.gke.io/gcp-service-account annotation in the Helm chart values."
value = google_service_account.collector.email
}

output "service_account_unique_id" {
description = "Unique ID (21-digit number) of the GCP service account. Share this with Nullify so it can be added to the federated AWS IAM role's trust-policy allowlist."
value = google_service_account.collector.unique_id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
variable "project_id" {
description = "GCP project ID where the GKE cluster runs and where the collector service account will be created."
type = string
}

variable "service_account_name" {
description = "Name of the GCP service account to create. The default matches the Helm chart's expectations."
type = string
default = "nullify-k8s-collector"
validation {
condition = can(regex("^[a-z][a-z0-9-]{4,28}[a-z0-9]$", var.service_account_name))
error_message = "service_account_name must be 6-30 characters, start with a letter, and contain only lowercase letters, digits, and hyphens."
}
}

variable "k8s_namespace" {
description = "Kubernetes namespace where the Nullify k8s-collector is deployed. Must match serviceAccount.namespace in the Helm chart values."
type = string
default = "nullify"
}

variable "k8s_service_account_name" {
description = "Kubernetes ServiceAccount name used by the collector pods. Must match serviceAccount.name in the Helm chart values."
type = string
default = "nullify-k8s-collector-sa"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.3"

required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0"
}
}
}
2 changes: 1 addition & 1 deletion helm-charts/nullify-k8s-collector/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: nullify-k8s-collector
description: A Helm chart for Nullify's Kubernetes information collector for cloud security scanning
type: application
version: 0.1.2
version: 0.2.0
maintainers:
- name: Nullify
url: https://nullify.ai
Expand Down
106 changes: 100 additions & 6 deletions helm-charts/nullify-k8s-collector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,44 @@ This Helm chart deploys a Kubernetes collector for the Nullify platform to gathe
- Kubernetes 1.16+
- Helm 3.0+

## Supported platforms

The same chart runs on **EKS** and **GKE**. Select the platform via `cloudProvider`:

| `cloudProvider` | Cluster | How the collector authenticates to AWS |
| --------------- | -------- | --------------------------------------------------- |
| `aws` (default) | EKS | IRSA — ServiceAccount → AWS IAM role |
| `gcp` | GKE | Workload Identity → `sts:AssumeRoleWithWebIdentity` |

In both cases the collector uploads cluster metadata to the same Nullify-managed
S3 bucket. No long-lived AWS credential is stored in the customer cluster.

## Configuration

The following table lists the configurable parameters of the chart and their default values.

> **Required values**: `collector.clusterName`, `collector.s3.bucket`, `collector.kms.keyArn`, and `serviceAccount.annotations.eks.amazonaws.com/role-arn` must be configured before deployment. Get these from the Nullify configure page.
> **Required values**: `collector.clusterName`, `collector.s3.bucket`, `collector.kms.keyArn`.
> Platform-specific required values are listed below. Get these from the Nullify configure page.

| Parameter | Description | Default |
|-----------|-------------|---------|
| `cloudProvider` | Platform the collector runs on: `aws` (EKS) or `gcp` (GKE) | `aws` |
| `serviceAccount.create` | If true, create a new service account | `true` |
| `serviceAccount.annotations` | Annotations for the service account (IAM role ARN from Nullify configure page) | `eks.amazonaws.com/role-arn: YOUR-NULLIFY-READ-ONLY-ROLE-ARN` |
| `serviceAccount.annotations` | Annotations for the service account. The chart renders only the annotation that matches `cloudProvider`. | See [values.yaml](./values.yaml) |
| `serviceAccount.name` | Name of the service account | `nullify-k8s-collector-sa` |
| `collector.image.repository` | Image repository | `public.ecr.aws/w4o2j2x4/integrations` |
| `collector.image.tag` | Image tag | `k8s-collector-latest` |
| `collector.image.pullPolicy` | Pull policy | `Always` |
| `collector.schedule` | CronJob schedule | `0 0 * * *` (daily at midnight) |
| `collector.s3.bucket` | S3 bucket for storing data (from Nullify configure page) | `nullify-death-star-dast-k8s` |
| `collector.s3.bucket` | S3 bucket for storing data (from Nullify configure page) | `YOUR-NULLIFY-S3-BUCKET` |
| `collector.s3.keyPrefix` | S3 key prefix | `k8s-collector` |
| `collector.aws.region` | AWS region | `ap-southeast-2` |
| `collector.clusterName` | Cluster name (must match your actual EKS cluster name) | `YOUR-CLUSTER-NAME` |
| `collector.aws.region` | AWS region | `us-east-1` |
| `collector.clusterName` | Cluster name (must match your actual cluster name) | `YOUR-CLUSTER-NAME` |
| `collector.kms.keyArn` | KMS key ARN for encryption (from Nullify configure page) | `""` |
| `collector.debug.enabled` | Enable debug logging for troubleshooting | `false` |
| `collector.gke.nullifyAwsRoleArn` | **GKE only.** Nullify-owned federated AWS IAM role ARN. | `""` |
| `collector.gke.audience` | **GKE only.** Token audience AWS STS checks against the OIDC provider. Do not change unless Nullify asks you to. | `sts.amazonaws.com` |
| `collector.gke.webIdentityTokenPath` | **GKE only.** In-pod path of the projected Workload Identity token. | `/var/run/secrets/tokens/gcp-sa-token` |
| `labels` | Additional labels for the collector resources | `null` |

## Security Context
Expand Down Expand Up @@ -79,9 +96,86 @@ serviceAccount:
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/NullifyCollectorRole"
```

### GKE with Workload Identity Federation

For GKE clusters, the collector authenticates to AWS without any long-lived credential.
The flow is:

1. GKE projects a Google-signed ServiceAccount token into the collector pod.
2. The collector forwards that token to AWS STS `AssumeRoleWithWebIdentity`.
3. AWS validates the token against the Google OIDC provider and returns short-lived
credentials for a Nullify-owned IAM role scoped to your S3 prefix.

#### One-time onboarding

1. **Enable Workload Identity on the cluster** (skip if already enabled):

```bash
gcloud container clusters update YOUR-CLUSTER \
--workload-pool=YOUR-PROJECT.svc.id.goog
```

2. **Create a GCP service account** dedicated to the collector. It does not need any
GCP IAM roles — its only job is to sign an OIDC token AWS STS will trust.

```bash
gcloud iam service-accounts create nullify-k8s-collector \
--display-name "Nullify Kubernetes Collector"
```

3. **Send its unique ID to Nullify.** The `uniqueId` is a 21-digit number, not the
email. Nullify adds it to the federated IAM role's trust-policy allowlist and
gives you back the role ARN.

```bash
gcloud iam service-accounts describe \
nullify-k8s-collector@YOUR-PROJECT.iam.gserviceaccount.com \
--format='value(uniqueId)'
```

4. **Bind the in-cluster Kubernetes ServiceAccount to the GCP SA** via Workload
Identity. The Kubernetes SA name/namespace below must match `serviceAccount.name`
and `serviceAccount.namespace` in your Helm values.

```bash
gcloud iam service-accounts add-iam-policy-binding \
nullify-k8s-collector@YOUR-PROJECT.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:YOUR-PROJECT.svc.id.goog[nullify/nullify-k8s-collector-sa]"
```

#### Helm values

```yaml
cloudProvider: gcp

collector:
clusterName: "my-gke-cluster"
aws:
region: "us-east-1" # AWS region of the Nullify S3 bucket
s3:
bucket: "your-nullify-bucket"
kms:
keyArn: "arn:aws:kms:us-east-1:123456789012:key/your-key-id"
gke:
# Provided by Nullify after step 3 of onboarding.
nullifyAwsRoleArn: "arn:aws:iam::123456789012:role/NullifyK8sCollectorRole"

serviceAccount:
annotations:
iam.gke.io/gcp-service-account: "nullify-k8s-collector@YOUR-PROJECT.iam.gserviceaccount.com"
```

Then install:

```bash
helm install nullify-k8s-collector ./nullify-k8s-collector -f values-gke.yaml
```

### Other Kubernetes Clusters

For non-EKS clusters, you'll need to provide AWS credentials through other means:
For clusters outside EKS and GKE, you'll need to provide AWS credentials through
other means:

- Using AWS environment variables in the pod
- Using instance profiles for nodes running on EC2
Expand Down
Loading
Loading