Skip to content

Database Security and Compliance - GCP Professional Cloud Database Engineer

Overview

Comprehensive guide to database security, access control, encryption, compliance, and security best practices for GCP database services. This guide covers security configurations for Cloud SQL, Cloud Spanner, Firestore, Bigtable, and Memorystore.

Key Security Principles: - Defense in depth with multiple security layers - Principle of least privilege for all access - Encryption at rest and in transit for all data - Comprehensive audit logging and monitoring - Compliance with industry standards (PCI-DSS, HIPAA, GDPR, SOC 2) - Network isolation and private connectivity - Automated security scanning and remediation

Exam Focus Areas: - Implementing CMEK encryption across all database services - Configuring IAM authentication and authorization - Setting up private networking and VPC Service Controls - Enabling and analyzing audit logs - Implementing compliance frameworks - Integrating Cloud DLP for sensitive data protection - Security monitoring and anomaly detection

Database Access Control and IAM

Cloud SQL IAM Authentication

Overview: - IAM database authentication eliminates password management - Users authenticate with their Google identity - Service accounts for application access - Integrated with Cloud IAM policies and audit logging - Supports PostgreSQL, MySQL, and SQL Server (limited)

Exam Tip: IAM authentication is preferred over traditional password-based authentication for security, auditability, and integration with organizational identity systems.

Database-level Access:

# Grant Cloud SQL Client role
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=user:dba@example.com \
  --role=roles/cloudsql.client

# Grant Cloud SQL Admin role
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=user:admin@example.com \
  --role=roles/cloudsql.admin

# Cloud SQL IAM database authentication
gcloud sql users create user@example.com \
  --instance=INSTANCE \
  --type=CLOUD_IAM_USER

# Service account authentication
gcloud sql users create sa@project.iam.gserviceaccount.com \
  --instance=INSTANCE \
  --type=CLOUD_IAM_SERVICE_ACCOUNT

# Create IAM user for PostgreSQL with specific database access
gcloud sql users create user@example.com \
  --instance=prod-db \
  --type=CLOUD_IAM_USER

# Create custom role with minimal permissions
gcloud iam roles create CloudSQLDataAnalyst \
  --project=PROJECT_ID \
  --title="Cloud SQL Data Analyst" \
  --description="Read-only access to specific databases" \
  --permissions=cloudsql.instances.connect,cloudsql.instances.get \
  --stage=GA

IAM Policy for Database Access:

# Cloud SQL instance-level IAM policy
bindings:
- role: roles/cloudsql.client
  members:
  - user:analyst@example.com
  - serviceAccount:app-backend@project.iam.gserviceaccount.com
  condition:
    title: "Production access during business hours"
    description: "Allow access only during business hours"
    expression: |
      request.time.getHours("America/New_York") >= 9 &&
      request.time.getHours("America/New_York") <= 17 &&
      request.time.getDayOfWeek("America/New_York") >= 1 &&
      request.time.getDayOfWeek("America/New_York") <= 5

- role: roles/cloudsql.admin
  members:
  - group:dba-team@example.com
  - user:senior-dba@example.com

- role: roles/cloudsql.instanceUser
  members:
  - serviceAccount:monitoring@project.iam.gserviceaccount.com

- role: roles/cloudsql.viewer
  members:
  - domain:example.com
  condition:
    title: "Read-only access"
    description: "View instance details only"
    expression: "true"

# Apply the policy
gcloud sql instances set-iam-policy INSTANCE policy.yaml

Connection with IAM:

import sqlalchemy
from google.cloud.sql.connector import Connector
from google.auth import default

# Get IAM credentials
credentials, project = default()

# PostgreSQL with IAM authentication
connector = Connector()

def getconn():
    conn = connector.connect(
        "project:region:instance",
        "pg8000",
        user="user@example.com",
        password="",  # Empty for IAM auth
        db="production_db",
        enable_iam_auth=True,
    )
    return conn

# Create engine with IAM auth
engine = sqlalchemy.create_engine(
    "postgresql+pg8000://",
    creator=getconn,
)

# MySQL with IAM authentication
def getconn_mysql():
    conn = connector.connect(
        "project:region:mysql-instance",
        "pymysql",
        user="user@example.com",
        password="",
        db="app_db",
        enable_iam_auth=True,
    )
    return conn

mysql_engine = sqlalchemy.create_engine(
    "mysql+pymysql://",
    creator=getconn_mysql,
)

# Service account authentication from application
import google.auth
from google.auth.transport.requests import Request

def get_iam_token():
    """Get IAM token for database authentication"""
    creds, project = google.auth.default(
        scopes=['https://www.googleapis.com/auth/sqlservice.admin']
    )
    creds.refresh(Request())
    return creds.token

# Use in connection string
token = get_iam_token()
# Token is used automatically by Cloud SQL Connector

Exam Tip: For the exam, know that IAM authentication requires enabling the Cloud SQL Admin API, creating IAM database users, and using the Cloud SQL Connector or Proxy. Traditional username/password authentication cannot be used with IAM users.

Cloud Spanner IAM

Overview: - Fine-grained access control at instance, database, and table levels - Role-based access control (RBAC) with predefined and custom roles - Conditional IAM policies for time-based and attribute-based access - Integration with VPC Service Controls for perimeter security

Predefined Roles: - roles/spanner.admin: Full control over instances and databases - roles/spanner.databaseAdmin: Database schema and data management - roles/spanner.databaseReader: Read-only access to database data - roles/spanner.databaseUser: Read and write access to database data - roles/spanner.viewer: View instance and database metadata

# Grant database roles
gcloud spanner databases add-iam-policy-binding DATABASE \
  --instance=INSTANCE \
  --member=user:developer@example.com \
  --role=roles/spanner.databaseReader

gcloud spanner databases add-iam-policy-binding DATABASE \
  --instance=INSTANCE \
  --member=serviceAccount:app@project.iam.gserviceaccount.com \
  --role=roles/spanner.databaseUser

# Fine-grained access control with conditions
gcloud spanner databases add-iam-policy-binding DATABASE \
  --instance=INSTANCE \
  --member=user:analyst@example.com \
  --role=roles/spanner.databaseReader \
  --condition='expression=resource.name.startsWith("projects/PROJECT/instances/INSTANCE/databases/DATABASE"),title=read-only-access'

# Custom role for specific permissions
gcloud iam roles create spannerReadOnlyCustomer \
  --project=PROJECT_ID \
  --title="Spanner Customer Reader" \
  --description="Read customer data only" \
  --permissions=spanner.databases.select,spanner.sessions.create,spanner.sessions.get \
  --stage=GA

# Database-level policy with multiple conditions
gcloud spanner databases set-iam-policy customer-db policy.yaml

Spanner IAM Policy Example:

# policy.yaml for Spanner database
bindings:
- role: roles/spanner.databaseReader
  members:
  - user:analyst@example.com
  - group:data-analysts@example.com
  condition:
    title: "Read access to specific tables"
    description: "Analysts can read customer and order tables"
    expression: |
      resource.name.endsWith('/tables/customers') ||
      resource.name.endsWith('/tables/orders')

- role: roles/spanner.databaseUser
  members:
  - serviceAccount:backend-api@project.iam.gserviceaccount.com
  - serviceAccount:batch-processor@project.iam.gserviceaccount.com

- role: roles/spanner.databaseAdmin
  members:
  - group:database-admins@example.com
  condition:
    title: "Admin access with MFA"
    description: "Require MFA for admin operations"
    expression: |
      request.auth.claims.auth_time > (request.time - duration('1h'))

- role: roles/spanner.backupAdmin
  members:
  - serviceAccount:backup-service@project.iam.gserviceaccount.com

Fine-Grained Access Control with Row-Level Security:

-- Spanner fine-grained access control using views
CREATE VIEW customer_view AS
SELECT customer_id, name, email, region
FROM customers
WHERE region = @region_param;

-- Grant access to view only
GRANT SELECT ON TABLE customer_view TO ROLE customer_viewer;

-- Application sets session variable
SET SESSION region = 'US-WEST';

-- User sees only their region's data
SELECT * FROM customer_view;

Exam Tip: Spanner supports IAM at the instance and database levels, but not at the table or row level through IAM alone. Use views and parameterized queries for fine-grained access control within the database.

Firestore Security Rules

Overview: - Client-side security rules for web and mobile applications - Server-side IAM for administrative access - Rules evaluated on every request - Support for authentication context, data validation, and custom functions - Real-time evaluation with no caching

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User data access
    match /users/{userId} {
      allow read, write: if request.auth.uid == userId;
    }

    // Shared data with field-level security
    match /posts/{postId} {
      allow read: if true;
      allow write: if request.auth != null
        && request.resource.data.authorId == request.auth.uid;
    }

    // Role-based access
    match /admin/{document=**} {
      allow read, write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    // Data validation
    match /orders/{orderId} {
      allow create: if request.auth != null
        && request.resource.data.keys().hasAll(['customerId', 'items', 'total'])
        && request.resource.data.total is number
        && request.resource.data.total > 0;
    }

    // Time-based access
    match /promotions/{promoId} {
      allow read: if request.time < resource.data.expiryDate;
      allow write: if request.auth != null
        && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'marketing';
    }

    // Hierarchical data access
    match /organizations/{orgId} {
      allow read: if request.auth != null
        && request.auth.uid in resource.data.members;

      match /projects/{projectId} {
        allow read, write: if request.auth != null
          && exists(/databases/$(database)/documents/organizations/$(orgId))
          && request.auth.uid in get(/databases/$(database)/documents/organizations/$(orgId)).data.members;
      }
    }

    // Content owner access
    match /content/{contentId} {
      allow read: if resource.data.visibility == 'public'
        || (request.auth != null && request.auth.uid == resource.data.ownerId);
      allow write: if request.auth != null && request.auth.uid == resource.data.ownerId;
      allow delete: if request.auth != null
        && (request.auth.uid == resource.data.ownerId
        || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin');
    }

    // Field-level validation
    match /profiles/{userId} {
      allow read: if request.auth.uid == userId;
      allow update: if request.auth.uid == userId
        && request.resource.data.email == resource.data.email  // Email cannot be changed
        && request.resource.data.createdAt == resource.data.createdAt  // Creation date immutable
        && request.resource.data.emailVerified == resource.data.emailVerified;  // Only admin can verify
    }

    // Custom functions for complex logic
    function isAdmin() {
      return request.auth != null &&
        get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    function isOwner(resource) {
      return request.auth != null && request.auth.uid == resource.data.ownerId;
    }

    function hasPermission(resource, permission) {
      return request.auth != null &&
        resource.data.permissions[request.auth.uid] == permission;
    }

    match /documents/{documentId} {
      allow read: if isAdmin() || isOwner(resource) || hasPermission(resource, 'read');
      allow write: if isAdmin() || isOwner(resource) || hasPermission(resource, 'write');
    }
  }
}

Firestore IAM for Server-Side Access:

# Grant Firestore user role
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:backend@project.iam.gserviceaccount.com \
  --role=roles/datastore.user

# Grant Firestore viewer role (read-only)
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=user:analyst@example.com \
  --role=roles/datastore.viewer

# Custom role for specific operations
gcloud iam roles create firestoreReader \
  --project=PROJECT_ID \
  --title="Firestore Reader" \
  --description="Read-only access to Firestore" \
  --permissions=datastore.entities.get,datastore.entities.list \
  --stage=GA

Exam Tip: Security rules apply to client SDK access (web/mobile), while IAM roles apply to server-side access through Admin SDK and REST API. For production systems, use both: security rules for client access and IAM for server/admin access.

Bigtable IAM

Overview: - Instance and table-level access control - No row-level security (implement in application layer) - Predefined roles for different access patterns - Integration with VPC Service Controls

# Grant instance-level access
gcloud bigtable instances add-iam-policy-binding INSTANCE \
  --member=user:engineer@example.com \
  --role=roles/bigtable.reader

# Application access with write permissions
gcloud bigtable instances add-iam-policy-binding INSTANCE \
  --member=serviceAccount:app@project.iam.gserviceaccount.com \
  --role=roles/bigtable.user

# Admin access for table management
gcloud bigtable instances add-iam-policy-binding INSTANCE \
  --member=group:bigtable-admins@example.com \
  --role=roles/bigtable.admin

# Viewer for monitoring
gcloud bigtable instances add-iam-policy-binding INSTANCE \
  --member=serviceAccount:monitoring@project.iam.gserviceaccount.com \
  --role=roles/bigtable.viewer

Bigtable Application-Level Access Control:

from google.cloud import bigtable

def read_with_prefix_filter(project_id, instance_id, table_id, user_id):
    """Implement row-level access control in application"""
    client = bigtable.Client(project=project_id)
    instance = client.instance(instance_id)
    table = instance.table(table_id)

    # Only read rows with user's prefix
    row_prefix = f"user#{user_id}#".encode()

    partial_rows = table.read_rows(
        start_key=row_prefix,
        end_key=row_prefix + b'\xff'
    )

    for row in partial_rows:
        # User can only access their own data
        print(f"Row key: {row.row_key.decode()}")

Exam Tip: Bigtable doesn't support native row-level security. Implement access control through row key design (e.g., prefixing with user/tenant ID) and application logic. Use IAM for instance and table-level access.

Memorystore (Redis/Memcached) Access Control

# Enable Redis AUTH
gcloud redis instances create secure-redis \
  --region=us-central1 \
  --tier=standard \
  --auth-enabled

# Get AUTH string
gcloud redis instances get-auth-string secure-redis --region=us-central1

# IAM permissions for Memorystore
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:app@project.iam.gserviceaccount.com \
  --role=roles/redis.editor

# Connect with AUTH
redis-cli -h REDIS_IP -a AUTH_STRING

Database Encryption

Encryption at Rest - Overview

Key Concepts: - Default encryption: All GCP databases encrypted at rest by default with Google-managed keys - CMEK (Customer-Managed Encryption Keys): Customer controls key lifecycle through Cloud KMS - CSEK (Customer-Supplied Encryption Keys): Limited support, customer provides keys per operation - Application-layer encryption: Encrypt data before storing in database

When to Use CMEK: - Regulatory requirements for key management - Need to control key rotation and lifecycle - Compliance with specific security standards (PCI-DSS, HIPAA) - Multi-tenant isolation at encryption level - Ability to revoke access by disabling keys

Exam Tip: CMEK adds latency and cost but provides additional control. Default Google-managed encryption is sufficient for most use cases. Use CMEK when compliance or business requirements mandate customer control over keys.

Cloud SQL CMEK Encryption

Customer-Managed Encryption Keys (CMEK):

# Create KMS key
gcloud kms keyrings create db-keyring --location=us-central1
gcloud kms keys create db-key \
  --keyring=db-keyring \
  --location=us-central1 \
  --purpose=encryption

# Grant Cloud SQL service account access
gcloud kms keys add-iam-policy-binding db-key \
  --keyring=db-keyring \
  --location=us-central1 \
  --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-cloud-sql.iam.gserviceaccount.com \
  --role=roles/cloudkms.cryptoKeyEncrypterDecrypter

# Create Cloud SQL instance with CMEK
gcloud sql instances create encrypted-instance \
  --database-version=POSTGRES_14 \
  --tier=db-n1-standard-2 \
  --region=us-central1 \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/db-keyring/cryptoKeys/db-key

# Update existing instance to use CMEK (requires instance restart)
gcloud sql instances patch existing-instance \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/db-keyring/cryptoKeys/db-key

# Backups are automatically encrypted with same CMEK
gcloud sql backups create \
  --instance=encrypted-instance

# Verify encryption
gcloud sql instances describe encrypted-instance \
  --format="value(diskEncryptionConfiguration.kmsKeyName)"

Multi-Region CMEK Setup:

# Create key ring in multi-region location
gcloud kms keyrings create multi-region-keyring --location=us

# Create key with automatic rotation
gcloud kms keys create auto-rotate-key \
  --keyring=multi-region-keyring \
  --location=us \
  --purpose=encryption \
  --rotation-period=90d \
  --next-rotation-time=2024-12-01T00:00:00Z

# Grant Cloud SQL service account access in all regions
for region in us-central1 us-east1 us-west1; do
  PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format="value(projectNumber)")
  gcloud kms keys add-iam-policy-binding auto-rotate-key \
    --keyring=multi-region-keyring \
    --location=us \
    --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-cloud-sql.iam.gserviceaccount.com \
    --role=roles/cloudkms.cryptoKeyEncrypterDecrypter
done

Exam Tip: Cloud SQL CMEK applies to both primary instance and backups. When using CMEK, the KMS key must be in the same or multi-region location as the instance. Disabling or destroying the key makes the instance and backups inaccessible.

Cloud Spanner CMEK

# Create instance with CMEK
gcloud spanner instances create encrypted-spanner \
  --config=regional-us-central1 \
  --nodes=1 \
  --kms-key=projects/PROJECT/locations/us-central1/keyRings/db-keyring/cryptoKeys/db-key

# Grant Spanner service account access to KMS key
PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format="value(projectNumber)")
gcloud kms keys add-iam-policy-binding db-key \
  --keyring=db-keyring \
  --location=us-central1 \
  --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-cloud-spanner.iam.gserviceaccount.com \
  --role=roles/cloudkms.cryptoKeyEncrypterDecrypter

# Create database with inherited CMEK from instance
gcloud spanner databases create customer-db \
  --instance=encrypted-spanner \
  --ddl='CREATE TABLE Customers (
    CustomerId INT64 NOT NULL,
    Name STRING(100),
    Email STRING(100)
  ) PRIMARY KEY (CustomerId)'

# Backups also use CMEK
gcloud spanner backups create customer-backup \
  --instance=encrypted-spanner \
  --database=customer-db \
  --retention-period=7d

# Verify CMEK configuration
gcloud spanner instances describe encrypted-spanner \
  --format="value(config, encryptionConfig.kmsKeyName)"

Bigtable and Firestore CMEK:

# Bigtable instance with CMEK
gcloud bigtable instances create encrypted-bigtable \
  --cluster=encrypted-cluster \
  --cluster-zone=us-central1-a \
  --cluster-num-nodes=3 \
  --cluster-kms-key=projects/PROJECT/locations/us-central1/keyRings/db-keyring/cryptoKeys/db-key \
  --display-name="Encrypted Bigtable Instance"

# Firestore with CMEK (set at database creation)
gcloud firestore databases create \
  --location=us-central \
  --kms-key-name=projects/PROJECT/locations/us-central1/keyRings/db-keyring/cryptoKeys/db-key

Exam Tip: For Spanner, CMEK is set at the instance level and inherited by all databases and backups. For Bigtable, CMEK is set per cluster. Firestore CMEK is set at database creation and cannot be changed later.

Encryption in Transit (SSL/TLS)

Overview: - All GCP database services support TLS 1.2+ for encryption in transit - Cloud SQL can require SSL/TLS for all connections - Certificate-based authentication available for Cloud SQL - Spanner, Firestore, and Bigtable use TLS by default Cloud SQL SSL/TLS Configuration:

# Require SSL for Cloud SQL
gcloud sql instances patch prod-instance \
  --require-ssl

# Download server CA certificate
gcloud sql ssl-certs describe server-ca \
  --instance=prod-instance \
  --format="get(cert)" > server-ca.pem

# Create client certificate
gcloud sql ssl-certs create client-cert client-key.pem \
  --instance=prod-instance

# Download client certificate
gcloud sql ssl-certs describe client-cert \
  --instance=prod-instance \
  --format="get(cert)" > client-cert.pem

# Connect with SSL verification (PostgreSQL)
psql "sslmode=verify-ca sslrootcert=server-ca.pem \
      sslcert=client-cert.pem sslkey=client-key.pem \
      host=INSTANCE_IP dbname=DATABASE user=USER"

# Connect with SSL verification (MySQL)
mysql --ssl-ca=server-ca.pem \
      --ssl-cert=client-cert.pem \
      --ssl-key=client-key.pem \
      -h INSTANCE_IP -u USER -p DATABASE

# List all SSL certificates
gcloud sql ssl-certs list --instance=prod-instance

# Revoke client certificate
gcloud sql ssl-certs delete client-cert --instance=prod-instance

SSL Modes: - disable: No SSL (not recommended for production) - allow: Use SSL if available - prefer: Prefer SSL, fallback to non-SSL - require: Require SSL but don't verify certificate - verify-ca: Require SSL and verify certificate authority - verify-full: Require SSL and verify certificate + hostname

Python Connection with SSL:

import psycopg2
import ssl

# PostgreSQL with SSL
conn = psycopg2.connect(
    host="INSTANCE_IP",
    database="production_db",
    user="dbuser",
    password="password",
    sslmode="verify-ca",
    sslrootcert="/path/to/server-ca.pem",
    sslcert="/path/to/client-cert.pem",
    sslkey="/path/to/client-key.pem"
)

# Using Cloud SQL Connector with SSL
from google.cloud.sql.connector import Connector

connector = Connector()

def getconn():
    conn = connector.connect(
        "project:region:instance",
        "pg8000",
        user="user",
        password="pass",
        db="database",
        enable_ssl=True  # SSL enabled by default
    )
    return conn

engine = sqlalchemy.create_engine(
    "postgresql+pg8000://",
    creator=getconn,
)

Exam Tip: Cloud SQL Proxy and Cloud SQL Connector automatically use encrypted connections. For direct connections to public IP, always require SSL and use certificate verification in production. Client certificates provide additional authentication beyond username/password.

Application-Level Encryption (Field-Level Encryption)

from cryptography.fernet import Fernet
from google.cloud import secretmanager

def get_encryption_key():
    """Get encryption key from Secret Manager"""
    client = secretmanager.SecretManagerServiceClient()
    name = f"projects/PROJECT/secrets/db-encryption-key/versions/latest"
    response = client.access_secret_version(name=name)
    return response.payload.data

def encrypt_sensitive_data(data):
    """Encrypt data before storing in database"""
    key = get_encryption_key()
    f = Fernet(key)
    return f.encrypt(data.encode()).decode()

def decrypt_sensitive_data(encrypted_data):
    """Decrypt data after retrieving from database"""
    key = get_encryption_key()
    f = Fernet(key)
    return f.decrypt(encrypted_data.encode()).decode()

# Usage in database operations
ssn_encrypted = encrypt_sensitive_data(ssn)
# Store encrypted_ssn in database
# Later retrieve and decrypt
ssn = decrypt_sensitive_data(ssn_encrypted)

# Application-level encryption for Spanner
from google.cloud import spanner
from cryptography.fernet import Fernet

def store_encrypted_data(instance_id, database_id, customer_id, ssn, credit_card):
    """Store PII with field-level encryption"""
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    # Encrypt sensitive fields
    encryption_key = get_encryption_key()
    cipher = Fernet(encryption_key)

    encrypted_ssn = cipher.encrypt(ssn.encode()).decode()
    encrypted_cc = cipher.encrypt(credit_card.encode()).decode()

    with database.batch() as batch:
        batch.insert(
            table='customers',
            columns=('customer_id', 'ssn_encrypted', 'credit_card_encrypted'),
            values=[(customer_id, encrypted_ssn, encrypted_cc)]
        )

# Use Cloud KMS for envelope encryption
from google.cloud import kms

def envelope_encryption(project_id, location_id, key_ring_id, key_id, plaintext):
    """Envelope encryption using Cloud KMS"""
    client = kms.KeyManagementServiceClient()
    key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id)

    # Generate data encryption key (DEK)
    import os
    dek = os.urandom(32)

    # Encrypt data with DEK
    cipher = Fernet(base64.urlsafe_b64encode(dek))
    encrypted_data = cipher.encrypt(plaintext.encode())

    # Encrypt DEK with KMS
    encrypt_response = client.encrypt(
        request={'name': key_name, 'plaintext': dek}
    )
    encrypted_dek = encrypt_response.ciphertext

    return encrypted_data, encrypted_dek

Exam Tip: Use application-level encryption for highly sensitive data (SSN, credit cards, health records) that requires protection even if database is compromised. Combine with CMEK for defense in depth. Store encryption keys in Secret Manager or Cloud KMS, never in the database.

Network Security

Private IP Configuration

Cloud SQL Private IP:

# Create instance with private IP only
gcloud sql instances create private-instance \
  --database-version=POSTGRES_14 \
  --tier=db-n1-standard-2 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/vpc-network \
  --no-assign-ip

# Add private IP to existing instance
gcloud sql instances patch existing-instance \
  --network=projects/PROJECT/global/networks/vpc-network

# Enable Private Service Connect
gcloud services enable servicenetworking.googleapis.com

# Allocate IP range for private services
gcloud compute addresses create google-managed-services-vpc-network \
  --global \
  --purpose=VPC_PEERING \
  --prefix-length=16 \
  --network=vpc-network

# Create private connection
gcloud services vpc-peerings connect \
  --service=servicenetworking.googleapis.com \
  --ranges=google-managed-services-vpc-network \
  --network=vpc-network

Spanner and Bigtable Private Endpoints:

# Spanner uses Private Google Access by default
# Enable Private Google Access on subnet
gcloud compute networks subnets update subnet-name \
  --region=us-central1 \
  --enable-private-ip-google-access

# Configure Private Service Connect for Spanner
gcloud compute addresses create spanner-psc-address \
  --global \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --addresses=10.0.0.5 \
  --network=vpc-network

# Bigtable private endpoint configuration
gcloud compute addresses create bigtable-psc-address \
  --region=us-central1 \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --addresses=10.1.0.5 \
  --network=vpc-network

Cloud SQL Proxy

# Start Cloud SQL Proxy for private IP
cloud_sql_proxy -instances=PROJECT:REGION:INSTANCE=tcp:5432 \
  -ip_address_types=PRIVATE

# Start proxy with multiple instances
cloud_sql_proxy \
  -instances=PROJECT:REGION:prod-db=tcp:5432,PROJECT:REGION:analytics-db=tcp:5433 \
  -ip_address_types=PRIVATE

# Use Unix socket
cloud_sql_proxy -dir=/cloudsql -instances=PROJECT:REGION:INSTANCE

# Connect via Unix socket
psql "host=/cloudsql/PROJECT:REGION:INSTANCE dbname=DATABASE user=USER"

# Run as Cloud Run sidecar
gcloud run deploy app \
  --image=gcr.io/PROJECT/app \
  --add-cloudsql-instances=PROJECT:REGION:INSTANCE \
  --set-env-vars=DB_HOST=/cloudsql/PROJECT:REGION:INSTANCE

Cloud SQL Proxy with IAM Authentication:

# Proxy with automatic IAM token
cloud_sql_proxy -instances=PROJECT:REGION:INSTANCE=tcp:5432 \
  -enable_iam_login

# Application connects without password
psql "host=127.0.0.1 dbname=DATABASE user=user@example.com sslmode=disable"

Authorized Networks

# Add authorized network (use only for public IP)
gcloud sql instances patch instance \
  --authorized-networks=203.0.113.0/24

# Add multiple networks
gcloud sql instances patch instance \
  --authorized-networks=203.0.113.0/24,198.51.100.0/24,192.0.2.0/24

# Add network with expiration time
gcloud sql instances patch instance \
  --authorized-networks=203.0.113.50/32 \
  --authorized-networks-expiration-time=2024-12-31T23:59:59Z

# Remove all authorized networks
gcloud sql instances patch instance \
  --clear-authorized-networks

Exam Tip: Prefer private IP over authorized networks. Use authorized networks only for temporary access or when VPC connectivity isn't available. Each authorized network increases attack surface.

VPC Service Controls

# Create access policy
gcloud access-context-manager policies create \
  --organization=ORG_ID \
  --title="Database Security Policy"

# Create access level
gcloud access-context-manager levels create database_access \
  --policy=POLICY_ID \
  --title="Database Access Level" \
  --basic-level-spec=access-level.yaml

# access-level.yaml
# combiningFunction: AND
# conditions:
# - ipSubnetworks:
#   - 10.0.0.0/16
# - members:
#   - user:dba@example.com
#   - serviceAccount:app@project.iam.gserviceaccount.com

# Create service perimeter
gcloud access-context-manager perimeters create database_perimeter \
  --policy=POLICY_ID \
  --title="Database Perimeter" \
  --resources=projects/PROJECT_NUMBER \
  --restricted-services=sqladmin.googleapis.com,spanner.googleapis.com,bigtable.googleapis.com \
  --access-levels=database_access

# Add perimeter for data exfiltration protection
gcloud access-context-manager perimeters create secure_db_perimeter \
  --policy=POLICY_ID \
  --title="Secure Database Perimeter" \
  --resources=projects/PROD_PROJECT_NUMBER \
  --restricted-services=sqladmin.googleapis.com,spanner.googleapis.com \
  --access-levels=production_access \
  --ingress-policies=ingress-policy.yaml \
  --egress-policies=egress-policy.yaml

Exam Tip: VPC Service Controls prevent data exfiltration by creating security perimeters around database services. Critical for compliance requirements (HIPAA, PCI-DSS) and preventing insider threats.

Audit Logging and Monitoring

Enable Comprehensive Audit Logging

Enable Audit Logs:

# Enable data access logs for Cloud SQL
gcloud projects get-iam-policy PROJECT_ID > policy.yaml

# Edit policy.yaml to add audit config:
# auditConfigs:
# - auditLogConfigs:
#   - logType: ADMIN_READ
#   - logType: DATA_READ
#   - logType: DATA_WRITE
#   service: cloudsql.googleapis.com
# - auditLogConfigs:
#   - logType: ADMIN_READ
#   - logType: DATA_READ
#   - logType: DATA_WRITE
#   service: spanner.googleapis.com
# - auditLogConfigs:
#   - logType: ADMIN_READ
#   - logType: DATA_READ
#   - logType: DATA_WRITE
#   service: bigtableadmin.googleapis.com

gcloud projects set-iam-policy PROJECT_ID policy.yaml

# Enable PostgreSQL query logging (pgAudit)
gcloud sql instances patch instance \
  --database-flags=cloudsql.enable_pgaudit=on,pgaudit.log=all

# Enable MySQL query logging
gcloud sql instances patch mysql-instance \
  --database-flags=general_log=on,log_output=FILE

# Enable Cloud SQL audit logging
gcloud logging sinks create sql-audit-sink \
  bigquery.googleapis.com/projects/PROJECT/datasets/audit_logs \
  --log-filter='resource.type="cloudsql_database"'

Cloud Audit Logs Types: - Admin Activity logs: Always enabled, no charge, 400-day retention - Data Access logs: Must be enabled, charged, captures data reads/writes - System Event logs: Always enabled, no charge - Policy Denied logs: Always enabled when VPC-SC is used

Database-Specific Audit Logging

PostgreSQL pgAudit Configuration:

-- Enable pgAudit extension
CREATE EXTENSION pgaudit;

-- Configure audit logging
ALTER SYSTEM SET pgaudit.log = 'ddl, write, read';
ALTER SYSTEM SET pgaudit.log_catalog = 'off';
ALTER SYSTEM SET pgaudit.log_parameter = 'on';
ALTER SYSTEM SET pgaudit.log_relation = 'on';
ALTER SYSTEM SET pgaudit.log_statement_once = 'off';

-- Audit specific roles
ALTER ROLE app_user SET pgaudit.log = 'read, write';

-- Audit specific tables
CREATE TABLE sensitive_data (
    id SERIAL PRIMARY KEY,
    ssn VARCHAR(11),
    credit_card VARCHAR(20)
);

COMMENT ON TABLE sensitive_data IS 'pgaudit: read, write';

MySQL Audit Plugin:

-- Enable audit plugin
INSTALL PLUGIN audit_log SONAME 'audit_log.so';

-- Configure audit settings via flags
-- Set via gcloud:
-- --database-flags=audit_log_policy=ALL,audit_log_format=JSON

Cloud SQL Query Insights:

# Enable Query Insights for performance and security monitoring
gcloud sql instances patch instance \
  --insights-config-query-insights-enabled \
  --insights-config-query-string-length=1024 \
  --insights-config-query-plans-per-minute=5

Audit Log Analysis Queries

-- Query 1: All database access by user
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as user,
  protoPayload.methodName as action,
  protoPayload.resourceName as resource,
  resource.labels.database_id as database,
  protoPayload.request as details
FROM
  `project.dataset.cloudaudit_googleapis_com_data_access_*`
WHERE
  resource.type = "cloudsql_database"
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
ORDER BY timestamp DESC;

-- Query 2: Failed authentication attempts
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as attempted_user,
  protoPayload.status.message as error_message,
  protoPayload.requestMetadata.callerIp as source_ip,
  resource.labels.instance_id as instance
FROM
  `project.dataset.cloudaudit_googleapis_com_data_access_*`
WHERE
  resource.type = "cloudsql_database"
  AND protoPayload.status.code != 0
  AND protoPayload.methodName LIKE '%connect%'
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
ORDER BY timestamp DESC;

-- Query 3: Schema changes (DDL operations)
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as user,
  protoPayload.methodName as operation,
  resource.labels.database_id as database,
  JSON_EXTRACT_SCALAR(protoPayload.metadata, '$.statement') as sql_statement
FROM
  `project.dataset.cloudaudit_googleapis_com_data_access_*`
WHERE
  resource.type = "cloudsql_database"
  AND protoPayload.methodName LIKE '%ddl%'
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
ORDER BY timestamp DESC;

-- Query 4: Suspicious access patterns (after-hours access)
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as user,
  protoPayload.requestMetadata.callerIp as source_ip,
  COUNT(*) as query_count
FROM
  `project.dataset.cloudaudit_googleapis_com_data_access_*`
WHERE
  resource.type = "cloudsql_database"
  AND EXTRACT(HOUR FROM timestamp) NOT BETWEEN 6 AND 18  -- Outside 6 AM - 6 PM
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY timestamp, user, source_ip
HAVING query_count > 100
ORDER BY query_count DESC;

-- Query 5: Data export operations
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as user,
  protoPayload.methodName as operation,
  resource.labels.instance_id as instance,
  CAST(JSON_EXTRACT_SCALAR(protoPayload.metadata, '$.exportContext.fileType') AS STRING) as export_type,
  CAST(JSON_EXTRACT_SCALAR(protoPayload.metadata, '$.exportContext.uri') AS STRING) as destination
FROM
  `project.dataset.cloudaudit_googleapis_com_activity_*`
WHERE
  protoPayload.methodName = 'cloudsql.instances.export'
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
ORDER BY timestamp DESC;

-- Query 6: Spanner access patterns
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as user,
  resource.labels.database_id as database,
  protoPayload.requestMetadata.callerIp as source_ip,
  COUNT(*) as operation_count
FROM
  `project.dataset.cloudaudit_googleapis_com_data_access_*`
WHERE
  resource.type = "spanner_database"
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
GROUP BY timestamp, user, database, source_ip
ORDER BY operation_count DESC
LIMIT 100;

-- Query 7: IAM policy changes on databases
SELECT
  timestamp,
  protoPayload.authenticationInfo.principalEmail as changed_by,
  protoPayload.methodName as operation,
  resource.labels.instance_id as instance,
  JSON_EXTRACT(protoPayload.request, '$.policy.bindings') as new_bindings
FROM
  `project.dataset.cloudaudit_googleapis_com_activity_*`
WHERE
  protoPayload.methodName LIKE '%SetIamPolicy%'
  AND (resource.type = "cloudsql_database" OR resource.type = "spanner_instance")
  AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
ORDER BY timestamp DESC;

-- Query 8: Unusual data access volumes
WITH daily_access AS (
  SELECT
    DATE(timestamp) as access_date,
    protoPayload.authenticationInfo.principalEmail as user,
    COUNT(*) as daily_queries
  FROM
    `project.dataset.cloudaudit_googleapis_com_data_access_*`
  WHERE
    resource.type = "cloudsql_database"
    AND timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
  GROUP BY access_date, user
),
avg_access AS (
  SELECT
    user,
    AVG(daily_queries) as avg_queries,
    STDDEV(daily_queries) as stddev_queries
  FROM daily_access
  GROUP BY user
)
SELECT
  d.access_date,
  d.user,
  d.daily_queries,
  a.avg_queries,
  (d.daily_queries - a.avg_queries) / a.stddev_queries as z_score
FROM daily_access d
JOIN avg_access a ON d.user = a.user
WHERE (d.daily_queries - a.avg_queries) / a.stddev_queries > 3  -- More than 3 standard deviations
ORDER BY z_score DESC;

Security Monitoring and Alerting

# Create log-based metric for failed connections
gcloud logging metrics create failed_db_connections \
  --description="Failed database connection attempts" \
  --log-filter='resource.type="cloudsql_database"
    AND protoPayload.status.code!=0
    AND protoPayload.methodName=~".*connect.*"'

# Create alert policy for failed connections
gcloud alpha monitoring policies create \
  --notification-channels=CHANNEL_ID \
  --display-name="High Failed DB Connection Rate" \
  --condition-display-name="Failed connections > 10 in 5 min" \
  --condition-threshold-value=10 \
  --condition-threshold-duration=300s \
  --condition-aggregation-alignment-period=300s \
  --condition-aggregation-per-series-aligner=ALIGN_RATE

# Create alert for suspicious after-hours access
gcloud logging metrics create after_hours_access \
  --description="After-hours database access" \
  --log-filter='resource.type="cloudsql_database"
    AND protoPayload.methodName=~".*query.*"
    AND (EXTRACT(HOUR FROM timestamp) < 6 OR EXTRACT(HOUR FROM timestamp) > 18)'

# Security Command Center integration
gcloud services enable securitycenter.googleapis.com

# Enable Security Health Analytics
gcloud scc settings services enable \
  --organization=ORG_ID \
  --service=SECURITY_HEALTH_ANALYTICS

Exam Tip: Export audit logs to BigQuery for long-term retention and complex analysis. Create log-based metrics and alerts for security events. Use Query Insights for performance monitoring and identifying inefficient or suspicious queries.

Compliance Frameworks

PCI-DSS Compliance for Payment Data

Requirements: 1. Encrypt cardholder data at rest (CMEK required) 2. Encrypt cardholder data in transit (TLS 1.2+) 3. Implement strong access control (IAM + authentication) 4. Regularly monitor and test networks (audit logs + alerts) 5. Maintain secure systems (patching, hardening) 6. Implement network segmentation (VPC, Private IP)

PCI-DSS Configuration Checklist:

# 1. Enable CMEK encryption
gcloud kms keyrings create pci-keyring --location=us-central1
gcloud kms keys create payment-key \
  --keyring=pci-keyring \
  --location=us-central1 \
  --purpose=encryption \
  --rotation-period=90d

# 2. Create PCI-compliant Cloud SQL instance
gcloud sql instances create pci-compliant-db \
  --database-version=POSTGRES_14 \
  --tier=db-n1-standard-4 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/pci-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/pci-keyring/cryptoKeys/payment-key \
  --database-flags=cloudsql.enable_pgaudit=on,pgaudit.log=all \
  --backup-start-time=02:00 \
  --retained-backups-count=30 \
  --deletion-protection

# 3. Enable comprehensive audit logging
gcloud logging sinks create pci-audit-sink \
  bigquery.googleapis.com/projects/PROJECT/datasets/pci_audit_logs \
  --log-filter='resource.type="cloudsql_database" AND resource.labels.instance_id="pci-compliant-db"'

# 4. Implement VPC Service Controls
gcloud access-context-manager perimeters create pci_perimeter \
  --policy=POLICY_ID \
  --title="PCI-DSS Perimeter" \
  --resources=projects/PROJECT_NUMBER \
  --restricted-services=sqladmin.googleapis.com \
  --access-levels=pci_access_level

# 5. Create IAM policies with least privilege
gcloud sql instances set-iam-policy pci-compliant-db pci-iam-policy.yaml

PCI-DSS IAM Policy:

# pci-iam-policy.yaml
bindings:
- role: roles/cloudsql.client
  members:
  - serviceAccount:payment-app@project.iam.gserviceaccount.com
  condition:
    title: "Access only from production VPC"
    expression: |
      origin.ip in ["10.0.0.0/16"]

- role: roles/cloudsql.admin
  members:
  - group:dba-team@example.com
  condition:
    title: "Admin with MFA and time restriction"
    expression: |
      request.auth.claims.auth_time > (request.time - duration('1h')) &&
      request.time.getHours("America/New_York") >= 9 &&
      request.time.getHours("America/New_York") <= 17

Application-Level PCI Controls:

from google.cloud import kms, secretmanager
from cryptography.fernet import Fernet
import hashlib

class PCIDataHandler:
    """Handle PCI-compliant data storage and retrieval"""

    def __init__(self, project_id, kms_key_name):
        self.project_id = project_id
        self.kms_key_name = kms_key_name
        self.kms_client = kms.KeyManagementServiceClient()

    def tokenize_card(self, card_number):
        """Tokenize credit card number"""
        # Create irreversible hash as token
        token = hashlib.sha256(card_number.encode()).hexdigest()
        return token

    def encrypt_card_data(self, card_data):
        """Encrypt card data with KMS"""
        # Get DEK from KMS
        response = self.kms_client.encrypt(
            request={
                'name': self.kms_key_name,
                'plaintext': card_data.encode()
            }
        )
        return response.ciphertext

    def mask_card_number(self, card_number):
        """Mask card number for display (PCI requirement)"""
        return f"****-****-****-{card_number[-4:]}"

    def store_payment_data(self, conn, transaction_id, card_number, cvv, expiry):
        """Store payment data securely"""
        # Tokenize card number
        token = self.tokenize_card(card_number)

        # Encrypt sensitive data
        encrypted_card = self.encrypt_card_data(card_number)
        encrypted_cvv = self.encrypt_card_data(cvv)

        # Store only encrypted data and token
        with conn.cursor() as cursor:
            cursor.execute("""
                INSERT INTO payment_tokens (transaction_id, card_token, card_encrypted, cvv_encrypted, expiry)
                VALUES (%s, %s, %s, %s, %s)
            """, (transaction_id, token, encrypted_card, encrypted_cvv, expiry))

        # Return masked card for display
        return self.mask_card_number(card_number)

Exam Tip: For PCI-DSS compliance, use CMEK encryption, implement network isolation with private IP, enable comprehensive audit logging, use tokenization for card data, and implement VPC Service Controls to prevent data exfiltration.

HIPAA Compliance for Healthcare Data

Requirements: 1. Business Associate Agreement (BAA) with Google Cloud 2. Encryption at rest and in transit 3. Access controls and audit logging 4. Backup and disaster recovery 5. Incident response procedures

HIPAA-Compliant Configuration:

# Create HIPAA-compliant database
gcloud sql instances create hipaa-db \
  --database-version=POSTGRES_14 \
  --tier=db-custom-4-26624 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/healthcare-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/hipaa-keyring/cryptoKeys/phi-key \
  --database-flags=cloudsql.enable_pgaudit=on,pgaudit.log=all \
  --backup-start-time=03:00 \
  --retained-backups-count=90 \
  --deletion-protection \
  --maintenance-window-day=SUN \
  --maintenance-window-hour=2

# Enable audit logging for HIPAA
gcloud logging sinks create hipaa-audit-sink \
  bigquery.googleapis.com/projects/PROJECT/datasets/hipaa_audit_logs \
  --log-filter='resource.type="cloudsql_database" AND resource.labels.instance_id="hipaa-db"'

# Set retention policy (7 years for HIPAA)
bq update --default_table_expiration=220752000 PROJECT:hipaa_audit_logs

PHI De-identification with Cloud DLP:

from google.cloud import dlp_v2

def de_identify_phi(project, content):
    """De-identify PHI using Cloud DLP"""
    dlp = dlp_v2.DlpServiceClient()

    # Define de-identification config
    deidentify_config = {
        "info_type_transformations": {
            "transformations": [
                {
                    "info_types": [
                        {"name": "PERSON_NAME"},
                        {"name": "US_SOCIAL_SECURITY_NUMBER"},
                        {"name": "PHONE_NUMBER"}
                    ],
                    "primitive_transformation": {
                        "crypto_hash_config": {
                            "crypto_key": {
                                "kms_wrapped": {
                                    "wrapped_key": WRAPPED_KEY,
                                    "crypto_key_name": KMS_KEY_NAME
                                }
                            }
                        }
                    }
                }
            ]
        }
    }

    response = dlp.deidentify_content(
        request={
            "parent": f"projects/{project}",
            "deidentify_config": deidentify_config,
            "item": {"value": content}
        }
    )

    return response.item.value

# Scan database for PHI
def scan_database_for_phi(project_id, instance_connection_name):
    """Scan database tables for unprotected PHI"""
    dlp = dlp_v2.DlpServiceClient()

    # Configure DLP job
    inspect_job = {
        'inspect_config': {
            'info_types': [
                {'name': 'PERSON_NAME'},
                {'name': 'US_SOCIAL_SECURITY_NUMBER'},
                {'name': 'PHONE_NUMBER'},
                {'name': 'EMAIL_ADDRESS'},
                {'name': 'DATE_OF_BIRTH'},
                {'name': 'MEDICAL_RECORD_NUMBER'}
            ],
            'min_likelihood': dlp_v2.Likelihood.POSSIBLE,
            'limits': {'max_findings_per_request': 100}
        },
        'storage_config': {
            'cloud_sql_options': {
                'connection_name': instance_connection_name,
                'identifying_column': 'patient_id'
            }
        }
    }

    # Create DLP job
    response = dlp.create_dlp_job(
        request={
            'parent': f'projects/{project_id}',
            'inspect_job': inspect_job
        }
    )

    return response.name

HIPAA Access Control:

class HIPAAAccessControl:
    """Implement HIPAA-compliant access controls"""

    def __init__(self, db_conn):
        self.conn = db_conn

    def log_phi_access(self, user_id, patient_id, access_type, reason):
        """Log every PHI access (HIPAA requirement)"""
        with self.conn.cursor() as cursor:
            cursor.execute("""
                INSERT INTO phi_access_log
                (timestamp, user_id, patient_id, access_type, reason, ip_address)
                VALUES (NOW(), %s, %s, %s, %s, %s)
            """, (user_id, patient_id, access_type, reason, get_client_ip()))

    def check_access_authorization(self, user_id, patient_id):
        """Verify user is authorized to access patient data"""
        with self.conn.cursor() as cursor:
            cursor.execute("""
                SELECT COUNT(*) FROM patient_authorizations
                WHERE user_id = %s AND patient_id = %s
                AND expiry_date > NOW()
            """, (user_id, patient_id))

            return cursor.fetchone()[0] > 0

    def access_phi(self, user_id, patient_id, reason):
        """Access PHI with proper authorization and logging"""
        # Check authorization
        if not self.check_access_authorization(user_id, patient_id):
            self.log_phi_access(user_id, patient_id, 'DENIED', reason)
            raise PermissionError("User not authorized to access this patient's data")

        # Log access
        self.log_phi_access(user_id, patient_id, 'GRANTED', reason)

        # Retrieve PHI
        with self.conn.cursor() as cursor:
            cursor.execute("""
                SELECT * FROM patient_records
                WHERE patient_id = %s
            """, (patient_id,))

            return cursor.fetchone()

Exam Tip: For HIPAA compliance, sign a BAA with Google, use only HIPAA-eligible services (Cloud SQL, Spanner, Firestore, BigQuery), implement CMEK encryption, enable comprehensive audit logging with 7-year retention, use Cloud DLP for PHI detection, and implement strict access controls with audit trails.

GDPR Compliance for EU Data

Data Residency Requirements:

# Create database in EU region
gcloud sql instances create gdpr-compliant-db \
  --region=europe-west1 \
  --database-version=POSTGRES_14 \
  --network=projects/PROJECT/global/networks/eu-vpc \
  --no-assign-ip

# Organization policy to enforce EU regions
cat > eu-regions-policy.yaml <<EOF
name: projects/PROJECT_ID/policies/gcp.resourceLocations
spec:
  rules:
  - values:
      allowedValues:
      - in:eu-locations
EOF

gcloud resource-manager org-policies set-policy eu-regions-policy.yaml \
  --project=PROJECT_ID

# Spanner with EU region
gcloud spanner instances create gdpr-spanner \
  --config=regional-europe-west1 \
  --nodes=1 \
  --description="GDPR compliant Spanner instance"

GDPR Data Subject Rights Implementation:

from google.cloud import spanner, firestore, bigtable
from datetime import datetime
import json

class GDPRDataController:
    """Implement GDPR data subject rights"""

    def __init__(self, project_id):
        self.project_id = project_id
        self.spanner_client = spanner.Client(project=project_id)
        self.firestore_client = firestore.Client(project=project_id)
        self.bigtable_client = bigtable.Client(project=project_id)

    def export_user_data(self, user_id):
        """Right to data portability (Article 20)"""
        user_data = {}

        # Export from Cloud SQL
        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT * FROM users WHERE user_id = %s", (user_id,)
            )
            user_data['profile'] = cursor.fetchone()

            cursor.execute(
                "SELECT * FROM user_activity WHERE user_id = %s", (user_id,)
            )
            user_data['activity'] = cursor.fetchall()

        # Export from Firestore
        user_doc = self.firestore_client.collection('users').document(user_id).get()
        if user_doc.exists:
            user_data['firestore'] = user_doc.to_dict()

        # Export from Spanner
        instance = self.spanner_client.instance('prod-instance')
        database = instance.database('user-db')
        with database.snapshot() as snapshot:
            results = snapshot.execute_sql(
                "SELECT * FROM UserPreferences WHERE UserId = @user_id",
                params={'user_id': user_id},
                param_types={'user_id': spanner.param_types.STRING}
            )
            user_data['preferences'] = [dict(row) for row in results]

        # Create portable format
        return json.dumps(user_data, indent=2, default=str)

    def delete_user_data(self, user_id):
        """Right to erasure (Article 17)"""
        deletion_log = []

        # Delete from Cloud SQL
        with db.connect() as conn:
            cursor = conn.cursor()

            tables = ['user_activity', 'user_preferences', 'users']
            for table in tables:
                cursor.execute(
                    f"DELETE FROM {table} WHERE user_id = %s", (user_id,)
                )
                deletion_log.append(f"Deleted from {table}: {cursor.rowcount} rows")

        # Delete from Firestore
        user_ref = self.firestore_client.collection('users').document(user_id)
        user_ref.delete()
        deletion_log.append(f"Deleted Firestore document: users/{user_id}")

        # Delete from Spanner
        instance = self.spanner_client.instance('prod-instance')
        database = instance.database('user-db')
        with database.batch() as batch:
            batch.delete('UserPreferences', spanner.KeySet(keys=[[user_id]]))
            batch.delete('UserActivity', spanner.KeySet(all_=True))  # Delete all with user_id
        deletion_log.append(f"Deleted from Spanner")

        # Delete from Bigtable
        instance = self.bigtable_client.instance('prod-bigtable')
        table = instance.table('user-events')
        row_key_prefix = f"user#{user_id}#".encode()
        rows = table.read_rows(start_key=row_key_prefix, end_key=row_key_prefix + b'\xff')
        for row in rows:
            row.delete()
        deletion_log.append(f"Deleted from Bigtable")

        # Log deletion for compliance
        self.log_gdpr_action(user_id, 'DELETE', deletion_log)

        return deletion_log

    def anonymize_user_data(self, user_id):
        """Anonymize instead of delete when deletion not possible"""
        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                UPDATE users
                SET name = 'ANONYMIZED',
                    email = CONCAT('anonymized_', user_id, '@deleted.com'),
                    phone = NULL,
                    address = NULL,
                    anonymized_at = NOW()
                WHERE user_id = %s
            """, (user_id,))

    def rectify_user_data(self, user_id, corrections):
        """Right to rectification (Article 16)"""
        with db.connect() as conn:
            cursor = conn.cursor()
            for field, value in corrections.items():
                cursor.execute(f"""
                    UPDATE users
                    SET {field} = %s, updated_at = NOW()
                    WHERE user_id = %s
                """, (value, user_id))

        self.log_gdpr_action(user_id, 'RECTIFY', corrections)

    def log_gdpr_action(self, user_id, action_type, details):
        """Log GDPR-related actions for compliance"""
        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT INTO gdpr_actions
                (timestamp, user_id, action_type, details, performed_by)
                VALUES (NOW(), %s, %s, %s, %s)
            """, (user_id, action_type, json.dumps(details), get_current_user()))

Exam Tip: For GDPR compliance, enforce EU region constraints with organization policies, implement data subject rights (access, rectification, erasure, portability), maintain audit logs, use data retention policies, and implement consent tracking.

SOC 2 Compliance

SOC 2 Trust Service Criteria Configuration:

# Security: Access controls and encryption
gcloud sql instances create soc2-db \
  --database-version=POSTGRES_14 \
  --region=us-central1 \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/soc2-key \
  --require-ssl \
  --deletion-protection

# Availability: High availability configuration
gcloud sql instances patch soc2-db \
  --availability-type=REGIONAL \
  --backup-start-time=02:00 \
  --retained-backups-count=30 \
  --enable-point-in-time-recovery

# Processing Integrity: Enable query monitoring
gcloud sql instances patch soc2-db \
  --insights-config-query-insights-enabled \
  --insights-config-query-string-length=4500

# Confidentiality: VPC Service Controls
gcloud access-context-manager perimeters create soc2_perimeter \
  --policy=POLICY_ID \
  --resources=projects/PROJECT_NUMBER \
  --restricted-services=sqladmin.googleapis.com,spanner.googleapis.com

# Privacy: Data classification and DLP
gcloud dlp inspect-templates create privacy-template \
  --display-name="SOC 2 Privacy Inspection" \
  --min-likelihood=POSSIBLE \
  --include-quote=true \
  --info-types=EMAIL_ADDRESS,PHONE_NUMBER,PERSON_NAME

Exam Tip: SOC 2 focuses on five trust service criteria: Security, Availability, Processing Integrity, Confidentiality, and Privacy. Implement controls for each area using GCP database security features.

Data Protection and Backup Security

Backup Encryption and Retention

# Cloud SQL automated backups with CMEK
gcloud sql instances create backup-secure-db \
  --database-version=POSTGRES_14 \
  --region=us-central1 \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/backup-keyring/cryptoKeys/backup-key \
  --backup-start-time=02:00 \
  --retained-backups-count=60 \
  --retained-transaction-log-days=7 \
  --enable-point-in-time-recovery

# On-demand backup
gcloud sql backups create \
  --instance=backup-secure-db \
  --description="Pre-maintenance backup"

# Spanner backup with CMEK
gcloud spanner backups create quarterly-backup \
  --instance=prod-spanner \
  --database=customer-db \
  --retention-period=90d \
  --version-time=$(date -u +%Y-%m-%dT%H:%M:%SZ)

# Export encrypted backup to Cloud Storage
gcloud sql export sql backup-secure-db gs://secure-backups/backup-$(date +%Y%m%d).sql \
  --database=production_db

# Encrypt exports with CSEK
gsutil -o "GSUtil:encryption_key=$(cat encryption-key.txt)" \
  cp local-backup.sql gs://secure-backups/encrypted-backup.sql

Point-in-Time Recovery (PITR)

# Enable PITR for Cloud SQL
gcloud sql instances patch prod-db \
  --enable-point-in-time-recovery \
  --retained-transaction-log-days=7

# Restore to specific point in time
gcloud sql instances clone prod-db recovery-instance \
  --point-in-time='2024-12-15T10:30:00Z'

# Spanner PITR
gcloud spanner databases create recovery-db \
  --instance=prod-spanner \
  --clone=customer-db \
  --clone-time=2024-12-15T10:30:00Z

Deletion Protection

# Enable deletion protection
gcloud sql instances patch critical-db \
  --deletion-protection

# Prevent accidental deletion via IAM
gcloud sql instances set-iam-policy critical-db deletion-protection-policy.yaml
# deletion-protection-policy.yaml
bindings:
- role: roles/cloudsql.admin
  members:
  - group:dba-team@example.com
  condition:
    title: "Prevent deletion"
    expression: |
      api.getAttribute('cloudsql.googleapis.com/instance.delete', '') == ''

Exam Tip: Always enable deletion protection for production databases. Use CMEK for backup encryption. Enable PITR for Cloud SQL for faster recovery. Test recovery procedures regularly as part of DR planning.

Comprehensive Security Scenarios

Scenario 1: Multi-Tenant SaaS with Data Isolation

Requirements: - Separate data per tenant - Ensure no cross-tenant data access - Tenant-level encryption keys - Per-tenant audit logging - Cost allocation per tenant

Solution Architecture:

# Option 1: Separate database per tenant
for tenant in tenant_a tenant_b tenant_c; do
  # Create tenant-specific KMS key
  gcloud kms keys create ${tenant}-key \
    --keyring=tenant-keyring \
    --location=us-central1 \
    --purpose=encryption

  # Create tenant database
  gcloud sql instances create ${tenant}-db \
    --database-version=POSTGRES_14 \
    --tier=db-custom-2-7680 \
    --region=us-central1 \
    --network=projects/PROJECT/global/networks/tenant-vpc \
    --no-assign-ip \
    --require-ssl \
    --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/tenant-keyring/cryptoKeys/${tenant}-key \
    --labels=tenant=${tenant},environment=production

  # Tenant-specific IAM
  gcloud sql instances set-iam-policy ${tenant}-db ${tenant}-iam-policy.yaml

  # Tenant-specific audit log sink
  gcloud logging sinks create ${tenant}-audit-sink \
    bigquery.googleapis.com/projects/PROJECT/datasets/${tenant}_audit_logs \
    --log-filter="resource.type=\"cloudsql_database\" AND resource.labels.instance_id=\"${tenant}-db\""
done

# Option 2: Shared database with row-level security
# PostgreSQL Row-Level Security (RLS)
CREATE TABLE tenant_data (
    id SERIAL PRIMARY KEY,
    tenant_id VARCHAR(50) NOT NULL,
    data JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Enable RLS
ALTER TABLE tenant_data ENABLE ROW LEVEL SECURITY;

-- Create policy for tenant isolation
CREATE POLICY tenant_isolation_policy ON tenant_data
    USING (tenant_id = current_setting('app.current_tenant')::TEXT);

-- Application sets tenant context
SET app.current_tenant = 'tenant_a';

-- Users can only see their tenant's data
SELECT * FROM tenant_data;  -- Only returns tenant_a data

Exam Tip: For multi-tenant architectures, separate databases provide strongest isolation but higher cost. Shared database with RLS is cost-effective but requires careful implementation. Use labels for cost allocation and tenant identification.

Scenario 2: Financial Services with PCI-DSS Compliance

Requirements: - Store payment card data securely - Meet PCI-DSS requirements - Cardholder Data Environment (CDE) isolation - Tokenization for card numbers - Comprehensive audit logging

Solution:

# Create isolated VPC for CDE
gcloud compute networks create pci-cde-vpc \
  --subnet-mode=custom

gcloud compute networks subnets create pci-cde-subnet \
  --network=pci-cde-vpc \
  --region=us-central1 \
  --range=10.0.1.0/24 \
  --enable-private-ip-google-access

# Create PCI-compliant database in CDE
gcloud sql instances create pci-payment-db \
  --database-version=POSTGRES_14 \
  --tier=db-n1-highmem-4 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/pci-cde-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/pci-keyring/cryptoKeys/payment-key \
  --database-flags=cloudsql.enable_pgaudit=on,pgaudit.log=all,log_connections=on,log_disconnections=on \
  --backup-start-time=02:00 \
  --retained-backups-count=90 \
  --deletion-protection

# VPC Service Controls for CDE perimeter
gcloud access-context-manager perimeters create pci_cde_perimeter \
  --policy=POLICY_ID \
  --title="PCI CDE Perimeter" \
  --resources=projects/PCI_PROJECT_NUMBER \
  --restricted-services=sqladmin.googleapis.com,storage.googleapis.com \
  --access-levels=pci_cde_access \
  --enable-vpc-accessible-services \
  --vpc-allowed-services=sqladmin.googleapis.com

# Firewall rules for CDE
gcloud compute firewall-rules create deny-all-ingress-pci-cde \
  --network=pci-cde-vpc \
  --action=DENY \
  --rules=all \
  --source-ranges=0.0.0.0/0 \
  --priority=1000

gcloud compute firewall-rules create allow-app-to-db-pci-cde \
  --network=pci-cde-vpc \
  --action=ALLOW \
  --rules=tcp:5432 \
  --source-tags=payment-app \
  --target-tags=payment-db \
  --priority=500

Payment Data Handler:

import hashlib
from google.cloud import kms, secretmanager

class PCICompliantPaymentHandler:
    def __init__(self, project_id):
        self.project_id = project_id
        self.kms_client = kms.KeyManagementServiceClient()
        self.kms_key_name = f"projects/{project_id}/locations/us-central1/keyRings/pci-keyring/cryptoKeys/payment-key"

    def tokenize_pan(self, pan):
        """Create token for Primary Account Number (PAN)"""
        # Use one-way hash for tokenization
        token = hashlib.sha256(f"{pan}{SECRET_SALT}".encode()).hexdigest()
        return token

    def store_card_data(self, pan, cvv, expiry, cardholder_name):
        """Store card data with PCI compliance"""
        # Tokenize PAN
        token = self.tokenize_pan(pan)

        # Encrypt full PAN with KMS (for refunds/disputes only)
        encrypted_pan = self.kms_client.encrypt(
            request={
                'name': self.kms_key_name,
                'plaintext': pan.encode()
            }
        ).ciphertext

        # Never store CVV (PCI requirement)
        # Store only last 4 digits of PAN and token
        last_four = pan[-4:]

        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT INTO payment_tokens
                (token, last_four, encrypted_pan, expiry, cardholder_name, created_at)
                VALUES (%s, %s, %s, %s, %s, NOW())
                RETURNING token_id
            """, (token, last_four, encrypted_pan, expiry, cardholder_name))

            token_id = cursor.fetchone()[0]

        # Log card storage event
        self.log_pci_event('CARD_STORED', token_id)

        return token

    def process_payment(self, token, amount):
        """Process payment using token"""
        # Retrieve encrypted PAN
        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT encrypted_pan, expiry
                FROM payment_tokens
                WHERE token = %s
            """, (token,))

            result = cursor.fetchone()
            if not result:
                raise ValueError("Invalid token")

            encrypted_pan, expiry = result

        # Decrypt PAN for payment processor
        pan = self.kms_client.decrypt(
            request={
                'name': self.kms_key_name,
                'ciphertext': encrypted_pan
            }
        ).plaintext.decode()

        # Log payment processing
        self.log_pci_event('PAYMENT_PROCESSED', token, amount)

        # Send to payment processor (implementation specific)
        return self.call_payment_processor(pan, expiry, amount)

    def log_pci_event(self, event_type, *args):
        """Log PCI-relevant events for audit"""
        with db.connect() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT INTO pci_audit_log
                (timestamp, event_type, details, user_id, ip_address)
                VALUES (NOW(), %s, %s, %s, %s)
            """, (event_type, str(args), get_current_user(), get_client_ip()))

Scenario 3: Healthcare Platform with HIPAA Compliance

Requirements: - Store PHI (Protected Health Information) - HIPAA compliance requirements - BAA with Google Cloud - Minimum necessary access - Comprehensive audit trails

Solution:

# Create HIPAA-compliant infrastructure
gcloud sql instances create hipaa-ehr-db \
  --database-version=POSTGRES_14 \
  --tier=db-custom-8-32768 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/healthcare-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/hipaa-keyring/cryptoKeys/phi-key \
  --database-flags=cloudsql.enable_pgaudit=on,pgaudit.log=all,log_statement=ddl,log_connections=on \
  --availability-type=REGIONAL \
  --backup-start-time=03:00 \
  --retained-backups-count=365 \
  --deletion-protection

# Create Spanner for patient records
gcloud spanner instances create hipaa-patient-records \
  --config=regional-us-central1 \
  --nodes=3 \
  --kms-key=projects/PROJECT/locations/us-central1/keyRings/hipaa-keyring/cryptoKeys/phi-key \
  --description="HIPAA-compliant patient records"

gcloud spanner databases create patient_db \
  --instance=hipaa-patient-records \
  --ddl-file=patient_schema.sql

Patient Data Access Control:

from google.cloud import spanner
from enum import Enum
import logging

class AccessReason(Enum):
    TREATMENT = "treatment"
    PAYMENT = "payment"
    OPERATIONS = "healthcare_operations"
    RESEARCH = "research_irb_approved"
    EMERGENCY = "emergency"

class HIPAAPatientDataAccess:
    def __init__(self, spanner_instance_id, database_id):
        self.spanner_client = spanner.Client()
        self.instance = self.spanner_client.instance(spanner_instance_id)
        self.database = self.instance.database(database_id)

    def access_patient_record(self, provider_id, patient_id, reason: AccessReason):
        """Access patient record with HIPAA compliance"""
        # Verify provider authorization
        if not self.verify_provider_access(provider_id, patient_id, reason):
            self.log_access_denial(provider_id, patient_id, reason)
            raise PermissionError("Provider not authorized for this patient")

        # Check for break-the-glass emergency access
        if reason == AccessReason.EMERGENCY:
            self.alert_compliance_team(provider_id, patient_id)

        # Log access (HIPAA requirement)
        self.log_phi_access(provider_id, patient_id, reason)

        # Retrieve minimum necessary information
        with self.database.snapshot() as snapshot:
            results = snapshot.execute_sql("""
                SELECT patient_id, name, dob, medical_record_number,
                       current_medications, allergies, active_diagnoses
                FROM patient_records
                WHERE patient_id = @patient_id
            """, params={'patient_id': patient_id},
                param_types={'patient_id': spanner.param_types.STRING})

            patient_data = list(results)[0]

        return patient_data

    def verify_provider_access(self, provider_id, patient_id, reason):
        """Verify provider has legitimate relationship with patient"""
        with self.database.snapshot() as snapshot:
            # Check if provider is assigned to patient
            results = snapshot.execute_sql("""
                SELECT COUNT(*) as count
                FROM patient_provider_relationships
                WHERE provider_id = @provider_id
                AND patient_id = @patient_id
                AND relationship_type = @reason
                AND relationship_end_date IS NULL
            """, params={
                'provider_id': provider_id,
                'patient_id': patient_id,
                'reason': reason.value
            }, param_types={
                'provider_id': spanner.param_types.STRING,
                'patient_id': spanner.param_types.STRING,
                'reason': spanner.param_types.STRING
            })

            count = list(results)[0][0]
            return count > 0 or reason == AccessReason.EMERGENCY

    def log_phi_access(self, provider_id, patient_id, reason):
        """Log PHI access for HIPAA audit trail"""
        with self.database.batch() as batch:
            batch.insert(
                table='phi_access_log',
                columns=('log_id', 'timestamp', 'provider_id', 'patient_id',
                        'access_reason', 'ip_address', 'user_agent'),
                values=[(
                    generate_uuid(),
                    spanner.COMMIT_TIMESTAMP,
                    provider_id,
                    patient_id,
                    reason.value,
                    get_client_ip(),
                    get_user_agent()
                )]
            )

    def de_identify_for_research(self, patient_ids):
        """De-identify PHI for research purposes"""
        from google.cloud import dlp_v2

        dlp = dlp_v2.DlpServiceClient()

        # Configure de-identification
        deidentify_config = {
            'record_transformations': {
                'field_transformations': [
                    {
                        'fields': [{'name': 'name'}, {'name': 'address'}],
                        'primitive_transformation': {
                            'replace_config': {
                                'new_value': {'string_value': '[REDACTED]'}
                            }
                        }
                    },
                    {
                        'fields': [{'name': 'dob'}],
                        'primitive_transformation': {
                            'date_shift_config': {
                                'upper_bound_days': 50,
                                'lower_bound_days': -50
                            }
                        }
                    }
                ]
            }
        }

        # Apply de-identification to dataset
        # Implementation specific to data format
        pass

Scenario 4: Global E-Commerce with Multi-Region Compliance

Requirements: - Store customer data in appropriate regions (GDPR, data residency) - PCI-DSS for payment processing - Data replication for performance - Region-specific compliance

Solution:

# US region for US customers
gcloud sql instances create ecommerce-us-db \
  --database-version=POSTGRES_14 \
  --tier=db-n1-highmem-4 \
  --region=us-central1 \
  --network=projects/PROJECT/global/networks/global-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/us-central1/keyRings/us-keyring/cryptoKeys/us-key

# EU region for EU customers (GDPR)
gcloud sql instances create ecommerce-eu-db \
  --database-version=POSTGRES_14 \
  --tier=db-n1-highmem-4 \
  --region=europe-west1 \
  --network=projects/PROJECT/global/networks/global-vpc \
  --no-assign-ip \
  --require-ssl \
  --disk-encryption-key=projects/PROJECT/locations/europe-west1/keyRings/eu-keyring/cryptoKeys/eu-key

# Global Spanner for order processing
gcloud spanner instances create global-orders \
  --config=nam-eur-asia1 \
  --processing-units=1000 \
  --kms-key=projects/PROJECT/locations/us-central1/keyRings/global-keyring/cryptoKeys/orders-key

# Database schema with region-specific data placement
CREATE TABLE customers (
    customer_id VARCHAR(36) PRIMARY KEY,
    region VARCHAR(10) NOT NULL,  -- US, EU, ASIA
    name VARCHAR(100),
    email VARCHAR(100),
    created_at TIMESTAMP,
    data_residency_region VARCHAR(20)
) INTERLEAVE IN PARENT customer_regions;

# Application routing logic
def get_customer_database(customer_region):
    """Route to appropriate regional database"""
    db_mapping = {
        'US': 'ecommerce-us-db',
        'EU': 'ecommerce-eu-db',
        'ASIA': 'ecommerce-asia-db'
    }
    return connect_to_database(db_mapping[customer_region])

Scenario 5: SaaS Application with API Key Management

Requirements: - Secure API key storage - API key rotation - Usage auditing - Rate limiting per key

Solution:

from google.cloud import secretmanager, firestore
import hashlib
import secrets

class APIKeyManager:
    def __init__(self, project_id):
        self.project_id = project_id
        self.secret_client = secretmanager.SecretManagerServiceClient()
        self.firestore_client = firestore.Client(project=project_id)

    def create_api_key(self, customer_id, key_name, permissions):
        """Create new API key for customer"""
        # Generate cryptographically secure API key
        api_key = f"sk_{secrets.token_urlsafe(32)}"

        # Hash API key for storage
        key_hash = hashlib.sha256(api_key.encode()).hexdigest()

        # Store hash in Firestore with metadata
        api_key_doc = self.firestore_client.collection('api_keys').document(key_hash)
        api_key_doc.set({
            'customer_id': customer_id,
            'key_name': key_name,
            'permissions': permissions,
            'created_at': firestore.SERVER_TIMESTAMP,
            'last_used_at': None,
            'usage_count': 0,
            'rate_limit': 1000,  # requests per hour
            'is_active': True
        })

        # Store full key in Secret Manager (for emergency recovery)
        secret_name = f"api-key-{key_hash[:16]}"
        parent = f"projects/{self.project_id}"
        self.secret_client.create_secret(
            request={
                'parent': parent,
                'secret_id': secret_name,
                'secret': {'replication': {'automatic': {}}}
            }
        )

        self.secret_client.add_secret_version(
            request={
                'parent': f"{parent}/secrets/{secret_name}",
                'payload': {'data': api_key.encode()}
            }
        )

        # Return API key (only shown once)
        return api_key

    def validate_api_key(self, api_key):
        """Validate API key and log usage"""
        key_hash = hashlib.sha256(api_key.encode()).hexdigest()

        api_key_doc = self.firestore_client.collection('api_keys').document(key_hash).get()

        if not api_key_doc.exists:
            self.log_invalid_key_attempt(api_key)
            return None

        key_data = api_key_doc.to_dict()

        if not key_data['is_active']:
            return None

        # Check rate limit
        if not self.check_rate_limit(key_hash, key_data):
            raise RateLimitExceeded("API rate limit exceeded")

        # Update usage statistics
        api_key_doc.reference.update({
            'last_used_at': firestore.SERVER_TIMESTAMP,
            'usage_count': firestore.Increment(1)
        })

        return key_data

    def rotate_api_key(self, old_key_hash):
        """Rotate API key (create new, deprecate old)"""
        old_key_doc = self.firestore_client.collection('api_keys').document(old_key_hash).get()
        old_key_data = old_key_doc.to_dict()

        # Create new key with same permissions
        new_key = self.create_api_key(
            old_key_data['customer_id'],
            old_key_data['key_name'] + '_rotated',
            old_key_data['permissions']
        )

        # Deprecate old key (don't delete for audit trail)
        old_key_doc.reference.update({
            'is_active': False,
            'deprecated_at': firestore.SERVER_TIMESTAMP,
            'replaced_by': hashlib.sha256(new_key.encode()).hexdigest()
        })

        return new_key

Scenario 6: Real-Time Analytics with Data Masking

Requirements: - Real-time analytics on customer data - Mask PII for analysts - Different access levels for different roles - Performance requirements

Solution:

-- PostgreSQL dynamic data masking views
CREATE VIEW customer_analytics_masked AS
SELECT
    customer_id,
    CASE
        WHEN current_setting('app.user_role', TRUE) = 'admin'
        THEN email
        ELSE regexp_replace(email, '(.{2}).*(@.*)', '\1***\2')
    END AS email,
    CASE
        WHEN current_setting('app.user_role', TRUE) = 'admin'
        THEN phone
        ELSE '***-***-' || right(phone, 4)
    END AS phone,
    total_purchases,
    total_spent,
    last_purchase_date,
    customer_segment
FROM customers;

-- Spanner with fine-grained access views
CREATE VIEW analyst_customer_view AS
SELECT
    customer_id,
    -- Anonymized identifiers
    FARM_FINGERPRINT(email) AS email_hash,
    FARM_FINGERPRINT(phone) AS phone_hash,
    -- Aggregate data only
    purchase_count,
    total_revenue,
    avg_order_value,
    customer_lifetime_value,
    -- Geographic data at region level only
    SUBSTR(postal_code, 1, 3) AS postal_code_prefix,
    state
FROM customer_details;

-- Grant access to masked view only
GRANT SELECT ON customer_analytics_masked TO ROLE analyst;
REVOKE SELECT ON customers FROM ROLE analyst;

Python Data Masking:

from google.cloud import bigquery

class DataMaskingService:
    def __init__(self, project_id):
        self.bq_client = bigquery.Client(project=project_id)

    def query_with_masking(self, query, user_role):
        """Execute query with role-based data masking"""
        # Apply masking based on role
        if user_role == 'analyst':
            masked_query = self.apply_analyst_masking(query)
        elif user_role == 'data_scientist':
            masked_query = self.apply_data_scientist_masking(query)
        else:
            masked_query = query

        return self.bq_client.query(masked_query).result()

    def apply_analyst_masking(self, query):
        """Apply masking for analyst role"""
        # Use BigQuery's data masking functions
        masking_rules = {
            'email': "REGEXP_REPLACE(email, r'(.{{2}}).*(@.*)', r'\\1***\\2')",
            'ssn': "CONCAT('***-**-', SUBSTR(ssn, -4))",
            'credit_card': "CONCAT('****-****-****-', SUBSTR(credit_card, -4))"
        }

        # Apply masking to query (simplified)
        for field, mask_expr in masking_rules.items():
            query = query.replace(field, f"{mask_expr} AS {field}")

        return query

Scenario 7: Incident Response and Forensics

Requirements: - Detect security incidents - Forensic analysis capabilities - Automated response - Compliance reporting

Solution:

from google.cloud import logging_v2, monitoring_v3
import json

class DatabaseSecurityIncidentResponse:
    def __init__(self, project_id):
        self.project_id = project_id
        self.logging_client = logging_v2.Client(project=project_id)
        self.monitoring_client = monitoring_v3.MetricServiceClient()

    def detect_sql_injection_attempt(self):
        """Detect potential SQL injection in query logs"""
        filter_str = '''
        resource.type="cloudsql_database"
        AND (
            textPayload=~".*(\\'|\\-\\-|;DROP|UNION SELECT).*"
            OR jsonPayload.statement=~".*(\\'|\\-\\-|;DROP|UNION SELECT).*"
        )
        '''

        entries = self.logging_client.list_entries(filter_=filter_str)

        incidents = []
        for entry in entries:
            incident = {
                'timestamp': entry.timestamp,
                'user': entry.labels.get('principalEmail'),
                'query': entry.json_payload.get('statement'),
                'source_ip': entry.http_request.remote_ip if entry.http_request else None,
                'severity': 'HIGH'
            }
            incidents.append(incident)

            # Trigger automated response
            self.respond_to_sql_injection(incident)

        return incidents

    def detect_unauthorized_access(self):
        """Detect unauthorized data access attempts"""
        filter_str = '''
        resource.type="cloudsql_database"
        AND protoPayload.status.code!=0
        AND protoPayload.methodName=~".*connect.*"
        '''

        entries = self.logging_client.list_entries(filter_=filter_str, max_results=100)

        # Detect brute force attempts
        failed_attempts = {}
        for entry in entries:
            user = entry.proto_payload.authentication_info.principal_email
            failed_attempts[user] = failed_attempts.get(user, 0) + 1

        # Alert on multiple failed attempts
        for user, count in failed_attempts.items():
            if count > 10:
                self.alert_security_team({
                    'type': 'BRUTE_FORCE',
                    'user': user,
                    'failed_attempts': count,
                    'severity': 'CRITICAL'
                })

                # Automated response: temporarily block user
                self.block_user_access(user, duration_minutes=30)

    def detect_data_exfiltration(self):
        """Detect large data exports"""
        filter_str = '''
        resource.type="cloudsql_database"
        AND protoPayload.methodName="cloudsql.instances.export"
        '''

        entries = self.logging_client.list_entries(filter_=filter_str)

        for entry in entries:
            export_size = entry.proto_payload.metadata.get('exportContext', {}).get('dataSize')

            if export_size and int(export_size) > 10 * 1024 * 1024 * 1024:  # > 10 GB
                self.alert_security_team({
                    'type': 'LARGE_DATA_EXPORT',
                    'user': entry.proto_payload.authentication_info.principal_email,
                    'size_gb': int(export_size) / (1024**3),
                    'destination': entry.proto_payload.metadata.get('exportContext', {}).get('uri'),
                    'severity': 'HIGH'
                })

    def respond_to_sql_injection(self, incident):
        """Automated response to SQL injection attempt"""
        # 1. Block the user
        self.block_user_access(incident['user'], duration_minutes=60)

        # 2. Alert security team
        self.alert_security_team(incident)

        # 3. Create incident ticket
        self.create_incident_ticket(incident)

        # 4. Preserve forensic evidence
        self.preserve_forensic_evidence(incident)

    def generate_forensic_report(self, incident_id, start_time, end_time):
        """Generate comprehensive forensic report"""
        report = {
            'incident_id': incident_id,
            'timeline': [],
            'affected_data': [],
            'user_actions': [],
            'recommendations': []
        }

        # Collect all relevant logs
        filter_str = f'''
        resource.type="cloudsql_database"
        AND timestamp>="{start_time}"
        AND timestamp<="{end_time}"
        '''

        entries = self.logging_client.list_entries(filter_=filter_str)

        for entry in entries:
            report['timeline'].append({
                'timestamp': str(entry.timestamp),
                'action': entry.proto_payload.method_name,
                'user': entry.proto_payload.authentication_info.principal_email,
                'details': str(entry.proto_payload)
            })

        # Analyze impact
        report['recommendations'] = self.generate_remediation_steps(report)

        return report

Scenario 8: Zero-Trust Database Access

Requirements: - Verify every access request - Context-aware access control - Device trust - Continuous authentication

Solution:

from google.cloud import iap
from google.auth.transport import requests
import jwt

class ZeroTrustDatabaseAccess:
    def __init__(self, project_id):
        self.project_id = project_id

    def verify_iap_jwt(self, iap_jwt, expected_audience):
        """Verify IAP JWT token"""
        try:
            decoded_jwt = jwt.decode(
                iap_jwt,
                algorithms=['ES256'],
                audience=expected_audience,
                options={'verify_signature': True}
            )

            return decoded_jwt
        except Exception as e:
            raise PermissionError(f"Invalid IAP JWT: {e}")

    def check_device_trust(self, device_id):
        """Verify device is trusted"""
        # Integrate with endpoint verification
        # Check device certificates, encryption status, etc.
        pass

    def evaluate_access_context(self, user_id, resource, context):
        """Context-aware access control"""
        score = 0

        # Check user risk score
        user_risk = self.get_user_risk_score(user_id)
        if user_risk < 0.3:
            score += 40

        # Check location
        if context.get('ip_address') in self.get_trusted_ip_ranges():
            score += 20

        # Check time of access
        if self.is_business_hours(context.get('timestamp')):
            score += 10

        # Check device trust
        if context.get('device_trusted'):
            score += 20

        # Check recent authentication
        if context.get('auth_age_minutes', 999) < 60:
            score += 10

        # Require score >= 70 for access
        return score >= 70

    def enforce_step_up_authentication(self, user_id, resource_sensitivity):
        """Require additional authentication for sensitive resources"""
        if resource_sensitivity == 'HIGH':
            # Require MFA
            if not self.verify_mfa(user_id):
                raise PermissionError("MFA required for high-sensitivity resources")

        if resource_sensitivity == 'CRITICAL':
            # Require MFA + approval
            if not self.verify_mfa(user_id) or not self.check_approval(user_id, resource):
                raise PermissionError("MFA and approval required for critical resources")

    def continuous_authentication(self, session_id):
        """Continuously verify user throughout session"""
        session = self.get_session(session_id)

        # Re-verify every 15 minutes
        if (datetime.now() - session['last_verification']).minutes > 15:
            # Check if user behavior matches baseline
            if not self.verify_behavior_pattern(session['user_id']):
                self.terminate_session(session_id)
                raise PermissionError("Anomalous behavior detected")

            session['last_verification'] = datetime.now()

Professional Database Engineer Exam Tips

Security Decision Framework

When choosing encryption options: 1. Google-managed encryption (default): Suitable for most use cases, no additional cost or latency 2. CMEK: Required for: - Regulatory compliance (PCI-DSS, HIPAA) - Key lifecycle control needed - Multi-tenant key isolation - Audit requirements for key usage 3. Application-level encryption: Required for: - Field-level encryption needs - End-to-end encryption requirements - Zero-knowledge architecture

When choosing network configuration: 1. Private IP only: Preferred for production, requires VPC setup 2. Private IP + Cloud SQL Proxy: Best balance of security and ease of use 3. Public IP + Authorized Networks: Temporary access only 4. Public IP + Cloud SQL Proxy: Development/testing scenarios

When implementing access control: 1. Use IAM authentication over password-based whenever possible 2. Implement least privilege with custom IAM roles 3. Use IAM conditions for time-based and attribute-based access 4. Separate admin and application service accounts 5. Regular access reviews (quarterly minimum)

When enabling audit logging: 1. Always enable Admin Activity logs (free, enabled by default) 2. Enable Data Access logs for: - Production databases - Databases with sensitive data - Compliance requirements (PCI, HIPAA, SOC 2) 3. Export to BigQuery for long-term retention and analysis 4. Create log-based metrics for security events 5. Set up alerting for critical security events

Compliance framework selection: - PCI-DSS: Payment card data - CMEK, network isolation, VPC-SC, audit logs - HIPAA: Healthcare data - BAA, CMEK, 7-year audit retention, DLP - GDPR: EU personal data - EU regions, data subject rights, consent tracking - SOC 2: SaaS trust - All five trust service criteria controls

Exam patterns to recognize: 1. Scenario asks for "most secure" β†’ Usually private IP + CMEK + IAM auth + VPC-SC 2. Scenario mentions "compliance" β†’ Enable comprehensive audit logging first 3. Scenario mentions "cost optimization" β†’ Consider default encryption vs CMEK trade-off 4. Scenario mentions "multi-tenant" β†’ Consider separate databases or RLS 5. Scenario mentions "minimal access" β†’ Use IAM conditions and custom roles 6. Scenario asks about "encryption in transit" β†’ Require SSL/TLS, use Cloud SQL Proxy 7. Scenario mentions "sensitive data" β†’ Consider application-level encryption + DLP 8. Scenario asks about "incident response" β†’ Export audit logs to BigQuery, create alerts

Common Exam Question Patterns

Pattern 1: "A healthcare company needs to store patient records..." - Answer involves: HIPAA, BAA, CMEK, audit logs, DLP, private networking

Pattern 2: "A payment processing application requires..." - Answer involves: PCI-DSS, CMEK, tokenization, VPC-SC, network isolation

Pattern 3: "A company with EU customers must..." - Answer involves: GDPR, EU regions, data subject rights, organization policies

Pattern 4: "Database access needs to be restricted to..." - Answer involves: IAM conditions, custom roles, time-based access, IP restrictions

Pattern 5: "Detecting unauthorized access to databases..." - Answer involves: Audit logs, log-based metrics, alerting, Security Command Center

Pattern 6: "Multi-tenant SaaS application with data isolation..." - Answer involves: Separate databases vs RLS, per-tenant CMEK, tenant-specific IAM

Key Services and Features to Know

Cloud SQL Security: - IAM database authentication (PostgreSQL, MySQL) - Private IP and VPC peering - Cloud SQL Proxy - SSL/TLS with client certificates - CMEK for encryption at rest - Automated backups with encryption - pgAudit for PostgreSQL - Deletion protection - Point-in-time recovery

Cloud Spanner Security: - IAM at instance and database level - Fine-grained access with IAM conditions - CMEK encryption - Automatic encryption in transit - VPC Service Controls support - Private Service Connect (PSC) - Audit logging - Backup encryption with CMEK

Firestore Security: - Security rules for client access - IAM for server-side access - CMEK encryption - Rules versioning - Test framework for security rules - Hierarchical security - Custom authentication claims

Bigtable Security: - IAM at instance level - Application-level row security - CMEK encryption per cluster - VPC Service Controls - Private networking via VPC - Replication encryption

Security Tools: - Cloud KMS for CMEK management - Secret Manager for secrets - Cloud DLP for sensitive data detection - VPC Service Controls for perimeter security - Security Command Center for findings - Cloud Armor for DDoS protection - Cloud Audit Logs for compliance

Study Approach

  1. Hands-on practice (Critical):
  2. Create databases with CMEK
  3. Configure IAM authentication
  4. Set up private IP and Cloud SQL Proxy
  5. Enable and query audit logs
  6. Implement VPC Service Controls
  7. Practice with Cloud DLP

  8. Scenario practice:

  9. Work through all 8 scenarios in this guide
  10. Understand when to use each security control
  11. Practice making trade-off decisions

  12. Compliance frameworks:

  13. Understand requirements for PCI-DSS, HIPAA, GDPR, SOC 2
  14. Know which GCP services are eligible for each compliance framework
  15. Understand BAA requirements

  16. Security patterns:

  17. Defense in depth
  18. Least privilege
  19. Zero trust architecture
  20. Encryption at rest and in transit
  21. Comprehensive audit logging

Additional Resources