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.
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. |
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