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
- Over-privileged accounts: Granting broad roles like
roles/spanner.adminto users who only need read access. - Public network exposure: Allowing connections from any IP address without restrictions.
- Unencrypted traffic: Failing to enforce TLS for all client connections.
- Service account misuse: Sharing service accounts across multiple applications instead of scoping them per workload.
- Audit gaps: Not enabling audit logs to track who accessed or modified data.
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.
roles/spanner.admin— Full control over all Spanner resources.roles/spanner.databaseAdmin— Manage databases within an instance, but cannot manage the instance itself.roles/spanner.instanceAdmin— Manage Spanner instances, but not databases or data.roles/spanner.databaseReader— Read data and schema from a database.roles/spanner.databaseUser— Read and write data within a database.roles/spanner.viewer— View Spanner resources in the console, but cannot access data.
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
- Apply least privilege: Always grant the minimum role needed. Prefer
databaseReaderordatabaseUseroveradminroles. - Use database-level IAM bindings: Grant permissions at the database level rather than the project or instance level whenever possible.
- Separate service accounts per workload: Each application should have its own service account with scoped permissions, making it easier to audit and revoke access.
- Enable VPC Service Controls: Create a service perimeter around Spanner to prevent data exfiltration, even if credentials are compromised.
- Use Private Service Connect or Private Google Access: Ensure that traffic between your applications and Spanner does not traverse the public internet.
- Leverage IAM Conditions: Use time-based and resource-based conditions to limit access windows and scope permissions dynamically.
- Enable audit logs: Turn on Data Access audit logs for Spanner and export them to BigQuery for analysis and compliance reporting.
- Regularly review IAM policies: Use the IAM recommender to identify and remove unused or overly broad permissions.
- Use fine-grained access control: For multi-tenant or shared databases, use IAM database roles to restrict access at the table and column level.
- Automate policy management: Use Infrastructure as Code tools like Terraform to manage IAM policies consistently and avoid manual configuration drift.
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.