gcp-cloudsql-explicit-ssl-cert-exposes-credential

cloudsql.sslCerts.create returns a client SSL certificate private key (one-time, in the API response), which serves as a database authentication credential.

derived gcp emits CanReadCredential

match (effective permission)

{ "action": "cloudsql.sslCerts.create", "resource_type": "google.sql.Instance" }

where

the principal calls cloudsql.sslCerts.create and receives the private key in the API response

emit

source typeIdentity
target typeCredential
source<principal>
target<SSL Certificate (private key)>
permissionscloudsql.sslCerts.create
conditionsiam_permission
state logicPOTENTIAL when cloudsql.sslCerts.create is confirmed effective. The attacker can CREATE the certificate and receive the private key in the response (one-time event). The key is NOT retrievable after creation (sslCerts.get/list do not return the private key). CONDITIONAL(iam_permission) when the permission is uncertain; BLOCKED when VPC-SC perimeter blocks sqladmin.googleapis.com.

Narrative

{principal.name} holds cloudsql.sslCerts.create on {instance.name} and can create a client SSL certificate, receiving the private key in the API response. The certificate serves as a database authentication credential.

Raw rule rules/derived/gcp/cloudsql.yaml

id: gcp-cloudsql-explicit-ssl-cert-exposes-credential
emits: CanReadCredential
description: cloudsql.sslCerts.create returns a client SSL certificate private key (one-time, in the API
  response), which serves as a database authentication credential.
match_effective_permission:
  action: cloudsql.sslCerts.create
  resource_type: google.sql.Instance
where:
- the principal calls cloudsql.sslCerts.create and receives the private key in the API response
emit:
  source_type: Identity
  target_type: Credential
  source: <principal>
  target: <SSL Certificate (private key)>
  permissions:
  - cloudsql.sslCerts.create
  conditions:
  - iam_permission
  state_logic: POTENTIAL when cloudsql.sslCerts.create is confirmed effective. The attacker can CREATE
    the certificate and receive the private key in the response (one-time event). The key is NOT retrievable
    after creation (sslCerts.get/list do not return the private key). CONDITIONAL(iam_permission) when
    the permission is uncertain; BLOCKED when VPC-SC perimeter blocks sqladmin.googleapis.com.
  false_positive_note: "The private key is returned ONLY at cert creation time in the API response field\
    \ certPrivateKey as a one-shot event. Listing existing certs (sslCerts.list/get) returns only the\
    \ public certificate and common name, NOT the private key. This is a POTENTIAL edge (attacker can\
    \ create the cert and get the key) \u2014 not ACTIVE (which would imply persistent access). After\
    \ obtaining the private key, the attacker still must satisfy DB-level cert auth requirements to authenticate:\
    \ - MySQL: REQUIRE X509 clause on the user account + CN matching policy - PostgreSQL: pg_hba.conf\
    \ cert method configured, CN mapped to a DB user These DB-level configs are not visible via the sqladmin\
    \ API; downgrade to CONDITIONAL(service_state) if DB-level cert auth cannot be confirmed. The Certificate\
    \ node serves as the credential. The downstream credential-chains rules (CredentialsFor, CanReadSecret)\
    \ model the authentication path."
  narrative: '{principal.name} holds cloudsql.sslCerts.create on {instance.name} and can create a client
    SSL certificate, receiving the private key in the API response. The certificate serves as a database
    authentication credential.'
  derived_from:
  - cloudsql.sslCerts.create effective permission on google.sql.Instance
  - API response field certPrivateKey contains the private key at cert-creation time
move · open · esc close