diff --git a/gcp-integration-setup/docs/permissions.md b/gcp-integration-setup/docs/permissions.md index c520389..9a17e27 100644 --- a/gcp-integration-setup/docs/permissions.md +++ b/gcp-integration-setup/docs/permissions.md @@ -54,14 +54,30 @@ Strict allowlist of `*.get` / `*.list` only. | `artifactregistry.repositories.get/list` | Artifact Registry repo metadata (no image content). | | `dns.managedZones.get/list` + `dns.resourceRecordSets.list` | Cloud DNS zone + record discovery. | | `apigateway.gateways.get/list` + `apigateway.apis.get/list` + `apigateway.apiconfigs.get/list` | API Gateway topology. | +| `storage.buckets.get/list` + `storage.buckets.getIamPolicy` | Cloud Storage bucket settings + bucket-level IAM. No `storage.objects.*`. | +| `secretmanager.secrets.get/list` | Secret Manager: secret name, labels, replication policy, rotation config. No `secretmanager.versions.access` (payloads). | +| `bigquery.datasets.get/list` + `bigquery.tables.get/list` + `bigquery.routines.get/list` | BigQuery dataset/table/routine schema + IAM. No `bigquery.tables.getData` (rows) and no `bigquery.jobs.create` (no query execution / billing). | +| `cloudbuild.buildTriggers.get/list` | Cloud Build trigger config (repo binding, file filter, substitutions). No build logs or artifacts. | +| `batch.jobs.get/list` | Cloud Batch job spec. No task logs or output artifacts. | +| `workflows.workflows.get/list` | Cloud Workflows: workflow definitions only. **Not** `workflows.executions.*` or `workflows.stepEntries.*` — execution arguments and step inputs/outputs are runtime data. | +| `datastore.databases.list` + `datastore.databases.getMetadata` | Firestore database list + metadata. **Not** `datastore.entities.*` — document contents are runtime data. (Firestore in Native and Datastore modes share the `datastore.*` IAM family.) | +| `aiplatform.endpoints.get/list` | Vertex AI endpoint deployment config. No `aiplatform.endpoints.predict` (inference) and no model/dataset/featurestore reads. | +| `securitycenter.sources.get/list` | Security Command Center source config (which detection sources are wired up). **Not** `securitycenter.findings.*` or `securitycenter.assets.*` — finding contents are runtime data. Org-scope only; harmless no-op at project scope. | ## What Nullify cannot do | Capability | Granted? | Why not | | --- | --- | --- | -| Read object data from Cloud Storage | No | `roles/storage.objectViewer` is intentionally **not** granted. We only see bucket metadata. | -| Read secret payloads from Secret Manager | No | `roles/secretmanager.secretAccessor` is intentionally **not** granted. We only see secret names and metadata. | -| Read BigQuery table rows | No | `roles/bigquery.dataViewer` is intentionally **not** granted. We only see dataset metadata. | +| Read object data from Cloud Storage | No | `roles/storage.objectViewer` is intentionally **not** granted. We only see bucket settings + bucket IAM. | +| Read secret payloads from Secret Manager | No | `roles/secretmanager.secretAccessor` is intentionally **not** granted. We only see secret names, labels, replication policy. | +| Read BigQuery table rows | No | `roles/bigquery.dataViewer` is intentionally **not** granted. We only see dataset/table schema + IAM. | +| Run BigQuery queries | No | `bigquery.jobs.create` is **not** granted. No query execution and no billable jobs. | +| Read Workflow execution payloads | No | `workflows.executions.*` and `workflows.stepEntries.*` are **not** granted. We only see workflow definitions, never the inputs/outputs of an execution. The predefined `roles/workflows.viewer` is **not** used because it would expose execution payloads. | +| Read Firestore document contents | No | `datastore.entities.*` is **not** granted. We only see the database list. The predefined `roles/datastore.viewer` is **not** used because it grants document reads. | +| Run Vertex AI inference | No | `aiplatform.endpoints.predict` and `computeTokens` are **not** granted. We see endpoint config only — not models, datasets, or featurestores. The broader `roles/aiplatform.viewer` is **not** used. | +| Read SCC findings | No | `securitycenter.findings.*` and `securitycenter.assets.*` are **not** granted. We only see which detection sources are configured. | +| Read Cloud Build logs or artifacts | No | Trigger config only. No build logs, artifacts, or source contents. | +| Read Cloud Batch task logs | No | Job spec only. No task logs or output artifacts. | | Modify your environment | No | Every role above is read-only. There are no write/admin roles. | | Run code or workloads | No | No `roles/run.invoker`, `roles/cloudfunctions.invoker` etc. | diff --git a/gcp-integration-setup/scripts/install.sh b/gcp-integration-setup/scripts/install.sh index cf0d9e8..b7af09e 100755 --- a/gcp-integration-setup/scripts/install.sh +++ b/gcp-integration-setup/scripts/install.sh @@ -104,7 +104,7 @@ CUSTOM_ROLE_TITLE="Nullify Cloud Connector (read-only)" CUSTOM_ROLE_DESCRIPTION="Read-only access to security-relevant config Nullify needs that is not covered by predefined viewer roles." # shellcheck disable=SC2089 -CUSTOM_ROLE_PERMISSIONS="compute.securityPolicies.get,compute.securityPolicies.list,accesscontextmanager.accessPolicies.get,accesscontextmanager.accessPolicies.list,accesscontextmanager.servicePerimeters.get,accesscontextmanager.servicePerimeters.list,orgpolicy.policies.list,orgpolicy.policy.get,alloydb.clusters.get,alloydb.clusters.list,alloydb.instances.get,alloydb.instances.list,file.instances.get,file.instances.list,redis.instances.get,redis.instances.list,memcache.instances.get,memcache.instances.list,artifactregistry.repositories.get,artifactregistry.repositories.list,dns.managedZones.get,dns.managedZones.list,dns.resourceRecordSets.list,apigateway.gateways.get,apigateway.gateways.list,apigateway.apis.get,apigateway.apis.list,apigateway.apiconfigs.get,apigateway.apiconfigs.list" +CUSTOM_ROLE_PERMISSIONS="compute.securityPolicies.get,compute.securityPolicies.list,accesscontextmanager.accessPolicies.get,accesscontextmanager.accessPolicies.list,accesscontextmanager.servicePerimeters.get,accesscontextmanager.servicePerimeters.list,orgpolicy.policies.list,orgpolicy.policy.get,alloydb.clusters.get,alloydb.clusters.list,alloydb.instances.get,alloydb.instances.list,file.instances.get,file.instances.list,redis.instances.get,redis.instances.list,memcache.instances.get,memcache.instances.list,artifactregistry.repositories.get,artifactregistry.repositories.list,dns.managedZones.get,dns.managedZones.list,dns.resourceRecordSets.list,apigateway.gateways.get,apigateway.gateways.list,apigateway.apis.get,apigateway.apis.list,apigateway.apiconfigs.get,apigateway.apiconfigs.list,storage.buckets.get,storage.buckets.list,storage.buckets.getIamPolicy,secretmanager.secrets.get,secretmanager.secrets.list,bigquery.datasets.get,bigquery.datasets.list,bigquery.tables.get,bigquery.tables.list,bigquery.routines.get,bigquery.routines.list,cloudbuild.buildTriggers.get,cloudbuild.buildTriggers.list,batch.jobs.get,batch.jobs.list,workflows.workflows.get,workflows.workflows.list,datastore.databases.getMetadata,datastore.databases.list,aiplatform.endpoints.get,aiplatform.endpoints.list,securitycenter.sources.get,securitycenter.sources.list" echo "==> Creating organisation custom role ${CUSTOM_ROLE_ID}" if gcloud iam roles describe "${CUSTOM_ROLE_ID}" --organization="${NULLIFY_ORG_ID}" >/dev/null 2>&1; then diff --git a/gcp-integration-setup/terraform/README.md b/gcp-integration-setup/terraform/README.md index 25fabea..8dc33f1 100644 --- a/gcp-integration-setup/terraform/README.md +++ b/gcp-integration-setup/terraform/README.md @@ -11,10 +11,14 @@ secrets. Nullify's OIDC issuer URL, with an `attribute_condition` pinned to your specific Nullify tenant id. - A custom role with read-only permissions on the long-tail services that - don't have a predefined viewer role (Cloud Armor, VPC Service Controls, - AlloyDB, Filestore, Memorystore, Cloud DNS, API Gateway, Artifact - Registry). Defined at the org for `scope = "organization" | "folder"`, - at the project for `scope = "projects"`. + don't have a suitable predefined viewer role (Cloud Armor, VPC Service + Controls, AlloyDB, Filestore, Memorystore, Cloud DNS, API Gateway, + Artifact Registry, Cloud Storage, Secret Manager, BigQuery, Cloud Build, + Cloud Batch, Cloud Workflows, Firestore, Vertex AI, Security Command + Center). Defined at the org for `scope = "organization" | "folder"`, + at the project for `scope = "projects"`. Strict allowlist of `*.get` / + `*.list` only — no data-plane reads (no object/secret/row/document + contents, no execution payloads, no inference, no findings). - IAM bindings granting the Nullify service account a curated set of predefined viewer roles plus the custom role above. Bound at organisation scope by default; folder and per-project scopes are also supported. diff --git a/gcp-integration-setup/terraform/modules/nullify-gcp-integration/main.tf b/gcp-integration-setup/terraform/modules/nullify-gcp-integration/main.tf index cb6060b..99513ec 100644 --- a/gcp-integration-setup/terraform/modules/nullify-gcp-integration/main.tf +++ b/gcp-integration-setup/terraform/modules/nullify-gcp-integration/main.tf @@ -18,7 +18,11 @@ # and network-topology metadata. There are NO data-plane permissions: # - storage: bucket metadata only, never object data # - secret manager: secret metadata only, never secret payloads -# - bigquery: dataset metadata only, never table rows +# - bigquery: schema/IAM only, never table rows +# - workflows: workflow definitions only, never execution inputs/outputs +# - firestore: database list only, never document contents +# - vertex ai: endpoint config only, never inference inputs/outputs +# - security command center: source config only, never finding contents # # See ../../docs/permissions.md for the full permission list + rationale. @@ -166,6 +170,62 @@ locals { "apigateway.apis.list", "apigateway.apiconfigs.get", "apigateway.apiconfigs.list", + + # Cloud Storage bucket settings + bucket-level IAM (no object data — + # storage.objects.* is intentionally not granted). + "storage.buckets.get", + "storage.buckets.list", + "storage.buckets.getIamPolicy", + + # Secret Manager: secret name, labels, replication policy, rotation + # config (no secretmanager.versions.access — payloads are never read). + "secretmanager.secrets.get", + "secretmanager.secrets.list", + + # BigQuery dataset/table/routine schema + IAM. No bigquery.tables.getData + # (row data) and no bigquery.jobs.create (no query execution / billing). + "bigquery.datasets.get", + "bigquery.datasets.list", + "bigquery.tables.get", + "bigquery.tables.list", + "bigquery.routines.get", + "bigquery.routines.list", + + # Cloud Build trigger config (repo binding, file filter, substitutions). + # No build logs, artifacts, or source contents. + "cloudbuild.buildTriggers.get", + "cloudbuild.buildTriggers.list", + + # Cloud Batch job spec. No task logs or output artifacts. + "batch.jobs.get", + "batch.jobs.list", + + # Cloud Workflows: workflow definitions only. workflows.executions.* and + # workflows.stepEntries.* are intentionally NOT granted — execution + # arguments and step inputs/outputs are runtime data. + "workflows.workflows.get", + "workflows.workflows.list", + + # Firestore: database list + metadata. datastore.entities.* is + # intentionally NOT granted — document contents are runtime data. + # (Note: GCP uses the datastore.* IAM family for Firestore in both + # Native and Datastore modes; firestore.* may be added later as GCP + # migrates the IAM surface.) + "datastore.databases.getMetadata", + "datastore.databases.list", + + # Vertex AI: endpoint deployment config only. aiplatform.endpoints.predict + # / computeTokens and all dataset/featurestore/model perms are + # intentionally NOT granted. + "aiplatform.endpoints.get", + "aiplatform.endpoints.list", + + # Security Command Center: source config (which detection sources are + # wired up). securitycenter.findings.* and securitycenter.assets.* are + # intentionally NOT granted — finding contents are runtime data. + # Org-scope only — at project scope these calls return empty harmlessly. + "securitycenter.sources.get", + "securitycenter.sources.list", ] }