Skip to content

feat(spanner): auth login support for Spanner Omni endpoints - #18273

Draft
sagnghos wants to merge 7 commits into
googleapis:mainfrom
sagnghos:sagnghos/authLogin
Draft

feat(spanner): auth login support for Spanner Omni endpoints#18273
sagnghos wants to merge 7 commits into
googleapis:mainfrom
sagnghos:sagnghos/authLogin

Conversation

@sagnghos

@sagnghos sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

⚠️ Note to Reviewers:

The files under google/cloud/spanner_v1/omni/proto/ (authentication_pb2*, login_pb2*, login_pb2_grpc*) are auto-generated.

The core hand-written logic is contained in the OPAQUE cryptography module, LoginClient, SpannerOmniCredentials, and the client/DB-API integrations.

This PR introduces native username/password authentication support for Spanner Omni endpoints using the OPAQUE password-authenticated key exchange (PAKE) protocol, maintaining parity with the Go and Java client implementations.

Java implementation - googleapis/google-cloud-java#13470
Go implementation - googleapis/google-cloud-go#20085

Key Changes:

  • Omni Login Protocol: Added generated protobufs (authentication_pb2*, login_pb2*, login_pb2_grpc*) and a gRPC LoginClient to handle the authentication handshake with Spanner Omni endpoints over the LoginService/Login bi-directional stream.
  • OPAQUE Protocol (opaque.py): Implements the client-side OPAQUE protocol utilizing NIST P-256 elliptic curve arithmetic (cryptography), Argon2id stretching with defensive parameter validation, RFC 9380 hash-to-curve / random oracle mapping, and constant-time MAC verification.
  • Security & Memory Safety: Ensures in-place zeroization of intermediate keys, stretched passwords, and shared secrets using mutable byte buffers inside finally blocks.
  • SpannerOmniCredentials & Interceptors (credentials.py): Implements SpannerOmniCredentials subclassing google.auth.credentials.Credentials to manage Bearer access token lifecycle, automatic expiry checks, and transparent token refreshes. Provides dedicated interceptors for both synchronous grpc and asynchronous grpc.aio channels across all RPC patterns (unary-unary, unary-stream, stream-unary, stream-stream).
  • Client & DB-API Integration:
    • Extended spanner_v1.Client and spanner_v1.AsyncClient to accept username, password, and instance_type="omni", automatically wiring up SpannerOmniCredentials.
    • Updated google.cloud.spanner_dbapi.connect() to accept username and password for DB-API connections.
    • Updated sync and async transport factories to attach Bearer auth interceptors to Omni gRPC channels.
  • Testing: Added comprehensive unit test suites covering the OPAQUE cryptographic engine, LoginClient state machine, credentials lifecycle/interceptors, DB-API connect options, and system test configurations for both sync and async client workflows against live Spanner Omni instances.

Running Integration Tests

To run integration tests against a live Spanner Omni instance with auth login, set the following environment variables:

export SPANNER_OMNI=localhost:15000
export CA_CERTIFICATE=/path/to/ca.crt
export SPANNER_OMNI_USER=admin
export SPANNER_OMNI_PASSWORD=admin

python -m pytest -v -s --disable-warnings tests/system/ \
  -o asyncio_mode=auto \
  -o asyncio_default_test_loop_scope=session \
  -o asyncio_default_fixture_loop_scope=session

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Spanner Omni authentication and connection utilities, implementing the OPAQUE protocol for login authentication. It adds a new SpannerOmniCredentials class, a LoginClient for gRPC communication, and cryptographic helpers in opaque.py, while updating connection and client classes to support username and password parameters. The review feedback highlights several important improvements, including resolving a potential NameError in connection.py when credentials is referenced before definition, ensuring the close() method of _RequestIterator is idempotent, cancelling active gRPC calls on exceptions to free resources, validating masked_response length early to prevent DoS vectors, and correcting type annotations for mutated headers parameters using MutableMapping.

Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/login_client.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/credentials.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/credentials.py Outdated
@sagnghos

sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Spanner Omni authentication using the OPAQUE protocol, adding username and password parameters to the connection and client interfaces, along with the necessary cryptographic utilities and OPAQUE login client. A critical performance issue was identified in the async gRPC interceptors, where calling the synchronous refresh() method directly blocks the asyncio event loop; it is recommended to run this synchronous call in a thread pool executor using loop.run_in_executor to prevent blocking.

Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/credentials.py Outdated
@sagnghos

sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Spanner Omni authentication using the OPAQUE protocol, adding username and password parameters to connection and client classes, implementing SpannerOmniCredentials, and adding the necessary cryptographic utilities and gRPC login client. The review feedback suggests improving thread safety in SpannerOmniCredentials by using a threading.Lock to synchronize token refreshes, implementing _perform_refresh_token instead of overriding refresh directly when subclassing CredentialsWithRegionalAccessBoundary, and optimizing the xor_bytes utility in opaque.py using fast integer bitwise operations.

Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/credentials.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py Outdated
@sagnghos

sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Spanner Omni authentication using the OPAQUE protocol, adding username and password parameters to the DB-API connection and client interfaces, implementing the OPAQUE login flow and cryptographic utilities, and updating transport helpers. The review feedback highlights critical security improvements in opaque.py to prevent creating immutable copies of sensitive data (plaintext passwords, blind scalars, and private keyshares) in memory, ensuring they can be zeroized in place. Additionally, the feedback suggests enhancing the robustness of the gRPC bidirectional streaming in LoginClient by safely handling premature stream closures to avoid unhandled StopIteration exceptions.

Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/login_client.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/login_client.py Outdated
Comment thread packages/google-cloud-spanner/google/cloud/spanner_v1/omni/login_client.py Outdated
@sagnghos

sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Spanner Omni authentication using the OPAQUE protocol, adding the SpannerOmniCredentials, LoginClient, and UserAuthenticator classes along with corresponding protobuf definitions and tests. It updates the DB-API connect function and the Spanner Client (both synchronous and asynchronous) to accept username and password parameters when instance_type is set to 'omni'. Feedback on the changes suggests improving the gRPC channel initialization in SpannerOmniCredentials to support mTLS using system default root certificates when ca_certificate is omitted but client certificates are provided, thereby avoiding breaking changes and preserving backwards compatibility.

@sagnghos

sagnghos commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for Spanner Omni authentication using the OPAQUE protocol. It adds new connection parameters (username and password) to the DB-API and client constructors, implements the SpannerOmniCredentials class with synchronous and asynchronous gRPC interceptors, and provides the cryptographic implementation of the OPAQUE protocol. One issue was identified in the cryptographic utilities where random_oracle_sha256 could crash due to an unvalidated iter_count exceeding 255 when encoding it as a single byte.

Comment on lines +295 to +298
if iter_count * hash_output_length > 130048:
raise ValueError(
f"Domain bit length must not be greater than 130048: {output_bit_length}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In random_oracle_sha256, i is encoded as a single byte using bytes([i]) on line 303. This restricts iter_count to a maximum of 255, as bytes([i]) will raise a ValueError for any i >= 256. Therefore, the maximum supported bit length is 255 * 256 = 65280 bits, rather than 130048 bits. The check should be updated to enforce iter_count <= 255 to prevent runtime crashes for larger domain sizes.

Suggested change
if iter_count * hash_output_length > 130048:
raise ValueError(
f"Domain bit length must not be greater than 130048: {output_bit_length}"
)
if iter_count > 255:
raise ValueError(
f"Domain bit length must not be greater than 65280: {output_bit_length}"
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant