gcp-cloudsql-explicit-ssl-cert-exposes-credential-explicit
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 (certPrivateKey field)
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 a client certificate and receive the private key in the API response (a 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 {instance.name} and can create a client SSL certificate, receiving the private key in the API response. The private key serves as a database authentication credential.
Raw rule rules/explicit/gcp-cloudsql.yaml
id: gcp-cloudsql-explicit-ssl-cert-exposes-credential-explicit
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.
applies_to:
- gcp
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 (certPrivateKey
field)
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
a client certificate and receive the private key in the API response (a 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), not ACTIVE (which would imply persistent re-access). After obtaining the private
key, the attacker still must satisfy DB-level cert auth requirements: - MySQL: REQUIRE X509 clause
on the user account (the CN is NOT a username in MySQL) - PostgreSQL: pg_hba.conf cert method + CN
mapped to a DB user account These DB-level configs are not visible via the sqladmin API; they must
be confirmed via data-plane queries or application documentation. The Certificate node represents
the exposed credential. Refer to credential-chains.yaml for the downstream CredentialsFor -> CanReadData
roll-up (PostgreSQL-specific auth paths).'
narrative: '{principal.name} holds cloudsql.sslCerts.create on instance {instance.name} and can create
a client SSL certificate, receiving the private key in the API response. The private key serves as
a database authentication credential.'
api_source: sqladmin.sslCerts.create response (certPrivateKey field)
evidence_field: cloudsql.sslCerts.create permission + API response certPrivateKey field present
derived_from:
- cloudsql.sslCerts.create effective permission on google.sql.Instance