โ† Back to DevBytes

Spanner Security: IAM Policies and Network Security

Introduction to Spanner Security

Google Cloud Spanner is a fully managed, horizontally scalable, relational database service that offers ACID transactions and SQL semantics at global scale. Because Spanner often stores mission-critical data, securing access to it is paramount. Spanner security revolves around two main pillars: Identity and Access Management (IAM) policies that control who can access resources, and network security controls that govern how and from where connections can be made. This tutorial walks through both pillars with practical examples, configuration snippets, and best practices.

Why Spanner Security Matters

Spanner databases frequently hold financial transactions, user profiles, inventory data, and other sensitive information. A misconfigured IAM policy or an exposed network endpoint can lead to data breaches, unauthorized modifications, or compliance violations. Properly configured IAM ensures the principle of least privilege, while network security ensures that only trusted sources can reach your Spanner instances. Together, they form a defense-in-depth strategy that protects data at the access layer.

Key Security Concerns

Understanding IAM Policies for Spanner

IAM is Google Cloud's unified access control system. Spanner resources follow a hierarchical model: Project → Spanner Instance → Database. IAM policies can be applied at any of these levels, and policies are inherited downward. A policy granted at the project level applies to all Spanner instances and databases within that project, while a policy granted at the database level applies only to that specific database.

Spanner IAM Roles

Google provides predefined roles tailored for Spanner. Choosing the right role for each user or service account is critical for least privilege.

Granting IAM Roles with gcloud

The most common way to manage IAM policies is through the gcloud CLI. Below is an example of granting a user database-level read access, which is more restrictive than instance-level access.

# Grant a user read access to a specific database
gcloud spanner databases add-iam-policy-binding my-database \
  --instance=my-instance \
  --member="user:analyst@example.com" \
  --role="roles/spanner.databaseReader"

# Grant a service account write access to a specific database
gcloud spanner databases add-iam-policy-binding my-database \
  --instance=my-instance \
  --member="serviceAccount:app-sa@my-project.iam.gserviceaccount.com" \
  --role="roles/spanner.databaseUser"

For automated infrastructure provisioning, Terraform is widely used. The following Terraform snippet shows how to grant IAM bindings at the database level.

resource "google_spanner_database_iam_binding" "reader" {
  instance = "my-instance"
  database = "my-database"
  role     = "roles/spanner.databaseReader"

  members = [
    "user:analyst@example.com",
    "group:data-team@example.com",
  ]
}

resource "google_spanner_database_iam_binding" "writer" {
  instance = "my-instance"
  database = "my-database"
  role     = "roles/spanner.databaseUser"

  members = [
    "serviceAccount:app-sa@my-project.iam.gserviceaccount.com",
  ]
}

Using IAM Conditions for Fine-Grained Access

IAM Conditions allow you to grant access based on attributes such as time, resource name, or request origin. This is useful for temporary access or for restricting access to specific databases within an instance. The following example grants a user access only during business hours on weekdays.

gcloud spanner databases add-iam-policy-binding my-database \
  --instance=my-instance \
  --member="user:contractor@example.com" \
  --role="roles/spanner.databaseReader" \
  --condition="title=BusinessHours,expression=request.time.getHours() >= 9 && request.time.getHours() <= 17 && request.time.getDayOfWeek() >= 1 && request.time.getDayOfWeek() <= 5"

Custom Roles for Least Privilege

If predefined roles grant too many permissions, you can create custom roles that include only the specific permissions needed. For example, a reporting service might only need to execute SELECT queries and read the schema, without any ability to modify data or schema.

# Create a custom role with minimal Spanner read permissions
gcloud iam roles create spannerReportReader \
  --project=my-project \
  --title="Spanner Report Reader" \
  --permissions="spanner.databases.read,spanner.databases.select,spanner.instances.get" \
  --stage=GA

# Assign the custom role to a service account
gcloud spanner databases add-iam-policy-binding my-database \
  --instance=my-instance \
  --member="serviceAccount:reporting-sa@my-project.iam.gserviceaccount.com" \
  --role="projects/my-project/roles/spannerReportReader"

Network Security for Spanner

While IAM controls who can authenticate, network security controls the pathways through which connections travel. Spanner supports several network security mechanisms including Private Service Connect, VPC Service Controls, and Private Google Access. These mechanisms ensure that traffic between your applications and Spanner never traverses the public internet.

Private Service Connect

Private Service Connect (PSC) allows you to access Google APIs and services through private IP addresses within your VPC. For Spanner, PSC endpoints provide a private connection path, reducing exposure to the public internet. To configure a PSC endpoint for Spanner, you create a forwarding rule that targets a Google service attachment.

# Create a Private Service Connect endpoint for Spanner
gcloud compute forwarding-rules create spanner-psc-endpoint \
  --global \
  --network=my-vpc \
  --address=spanner-private-ip \
  --target-service-attachment=projects/gcp-service-attachments/regions/us-central1/serviceAttachments/spanner-psc \
  --region=us-central1

Once the PSC endpoint is created, applications within the VPC can connect to Spanner using the private IP address. DNS records should be configured to route Spanner API requests through the PSC endpoint.

VPC Service Controls

VPC Service Controls add an additional security perimeter around your Google Cloud resources. They prevent data exfiltration by restricting access to Spanner based on the source network and identity. Even if an attacker obtains valid credentials, they cannot access Spanner from outside the defined service perimeter.

# Create a service perimeter that includes Spanner
gcloud access-context-manager perimeters create my_perimeter \
  --title="Spanner Security Perimeter" \
  --resources="projects/123456789012" \
  --restricted-services="spanner.googleapis.com" \
  --policy=1234567890

You can further restrict access by defining access levels that specify which IP ranges or identity groups are allowed within the perimeter.

# Create an access level allowing only corporate IP ranges
gcloud access-context-manager levels create corporate_network \
  --title="Corporate Network" \
  --basic-level-spec=access-level.yaml \
  --policy=1234567890

The access-level.yaml file defines the allowed IP CIDR ranges:

combiningFunction: AND
conditions:
  - ipSubnetworks:
    - "10.0.0.0/8"
    - "192.168.1.0/24"

Private Google Access

Private Google Access allows VMs without external IP addresses to reach Google APIs and services. When enabled on a subnet, resources in that subnet can communicate with Spanner using Google's internal network without public IP exposure.

# Enable Private Google Access on a subnet
gcloud compute networks subnets update my-subnet \
  --region=us-central1 \
  --enable-private-ip-google-access

Client-Side TLS and Connection Security

All connections to Spanner are encrypted using TLS by default. The Spanner client libraries handle certificate validation automatically. However, for applications that require additional verification, you can pin the Google API TLS certificate or use mutual TLS through a proxy. Below is a Python example showing a Spanner client connection with explicit transport settings.

from google.cloud import spanner

# Create a Spanner client with default TLS settings
# The client library automatically uses TLS for all connections
spanner_client = spanner.Client(project="my-project")

instance = spanner_client.instance("my-instance")
database = instance.database("my-database")

# Execute a simple query to verify connectivity
with database.snapshot() as snapshot:
    results = snapshot.execute_sql("SELECT 1")
    for row in results:
        print(f"Connection successful: {row[0]}")

Database-Level Fine-Grained Access Control

Beyond IAM, Spanner supports fine-grained access control at the database level through IAM database roles. This feature allows you to grant SELECT, INSERT, UPDATE, and DELETE permissions on specific tables or columns. This is particularly useful for multi-tenant applications or when different teams need access to different subsets of data within the same database.

Creating IAM Database Roles

IAM database roles are defined using SQL DDL statements. The following example creates a role that can only read from a specific table.

-- Create a database role with limited permissions
CREATE ROLE analytics_reader;

-- Grant SELECT on specific tables to the role
GRANT SELECT ON TABLE orders TO ROLE analytics_reader;
GRANT SELECT ON TABLE customers TO ROLE analytics_reader;

-- Grant the database role to an IAM principal
GRANT ROLE analytics_reader TO USER "analyst@example.com";

You can also restrict access at the column level for even finer control.

-- Create a role that can only see non-sensitive columns
CREATE ROLE limited_reader;

-- Grant SELECT on specific columns only
GRANT SELECT(id, name, created_at) ON TABLE customers TO ROLE limited_reader;

-- The role cannot access sensitive columns like ssn or email
GRANT ROLE limited_reader TO USER "support@example.com";

Auditing and Monitoring

Security is not complete without observability. Cloud Audit Logs capture all IAM policy changes and Spanner API calls. You should enable Data Access audit logs for Spanner to track who read or wrote data. These logs can be exported to BigQuery for long-term analysis or to Cloud Monitoring for alerting.

# Enable Data Access audit logs for Spanner via gcloud
gcloud projects update my-project \
  --no-enable-data-access-audit-logs

# Configure audit log settings explicitly
cat <<EOF > audit_config.yaml
auditConfigs:
  - service: spanner.googleapis.com
    auditLogConfigs:
      - logType: DATA_READ
      - logType: DATA_WRITE
      - logType: ADMIN_READ
EOF

gcloud projects update my-project --audit-logs-config=audit_config.yaml

Best Practices

Conclusion

Securing Google Cloud Spanner requires a layered approach that combines IAM policies for identity-based access control with network security controls for connection-level protection. By granting least-privilege roles at the appropriate resource level, using IAM Conditions and database roles for fine-grained control, and wrapping Spanner in VPC Service Controls and private networking, you can significantly reduce the risk of unauthorized access and data exfiltration. Pair these controls with comprehensive audit logging and regular policy reviews to maintain a strong security posture over time. Security is not a one-time configuration but an ongoing practice, and Spanner provides the tools needed to enforce it at every layer of the access stack.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles