google

package
v1.2.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

google — credentials and bearer tokens for TinyGo

cloud/google is what every Google Cloud client in this repository needs: credentials, bearer tokens, and the HTTP client the build selects. It is the counterpart of cloud/aws, and it is shaped by the one real difference between the two clouds.

AWS signs each request locally, and the signature never leaves the process. Google wants a bearer token, and the ordinary way to get one is a POST to oauth2.googleapis.com — which on this stack means a second TLS handshake to a second pool entry before the first real call can go out.

So the default here is a self-signed JWT: sign a claim set with the service account key, send it as the bearer value, and skip the token endpoint entirely.

creds, err := google.CredentialsFromEnv()          // GOOGLE_APPLICATION_CREDENTIALS
src, err := google.JWTTokenSource(creds, "https://datastore.googleapis.com/")
defer src.Close()

tokens := google.Cached(src)
token, err := tokens.Token(ctx)
req.Header.Set("Authorization", "Bearer "+token.Value)

Token sources

Source Round trips Links RSA code Where it fits
JWTTokenSource none yes the default; one audience per source
OAuth2TokenSource one, to the token endpoint yes deployments that require a real access token
MetadataTokenSource one, to the metadata server no GCE, Cloud Run, GKE
StaticTokenSource none no a device provisioned by a companion service, and tests

The audience for a self-signed JWT is the service host with a trailing slash, and it is per-service: a token minted for Datastore is not accepted by another API. aud and scope are mutually exclusive on that path, so JWTTokenSource sets only aud and OAuth2TokenSource sets only scope.

Cached refreshes 60 s before expiry, on the calling goroutine, under a mutex: one refresh is in flight and the others wait for it. There is no background refresher — it would outlive the client unless something stopped it, and TinyGo goroutines are not OS threads, so one cannot be relied on to make progress while another blocks in a socket call.

Signing

The RSA operation is not in this package. It is in internal/rsasign, which uses crypto/rsa on host Go and the OS crypto library on TinyGo builds. That split is about binary size: in a real client, forcing the pure-Go path costs 357 KB, which is more than the entire darwin HTTPS client.

Only JWTTokenSource and OAuth2TokenSource reference the signing code, so a binary built with StaticTokenSource or MetadataTokenSource drops it.

SignerBackend() reports which implementation this build selected; Backend reports the HTTP stack, matching cloud/aws.

RS256 only. ES256 is not implemented.

Clocks

One failure mode has no AWS equivalent and is worth stating plainly.

A self-signed JWT is only valid against the server's clock. A device whose clock is wrong mints a token the server rejects as expired, reported as UNAUTHENTICATED — which is not a retryable status. The likeliest cause of that status in the field is the clock, not the key.

A service client can call CachedSource.Invalidate and resend once on a 401, which is what nosql/datastore does. That recovers from a token that expired early; it does not recover from a clock that is hours out.

Credentials

CredentialsFromEnv reads the file named by GOOGLE_APPLICATION_CREDENTIALS. That is the whole of the resolution.

The full Application Default Credentials search also consults a well-known gcloud config path, the GCE metadata server, and external account files. Each is a different credential kind with a different failure mode, and a client this size is better off being told which one it has. MetadataTokenSource covers the GCE case explicitly.

ProjectIDFromEnv reads GOOGLE_CLOUD_PROJECT, then DATASTORE_PROJECT_ID. EmulatorHost("datastore") reads DATASTORE_EMULATOR_HOST, the variable the gcloud emulators set. That value carries no scheme; emulators speak plain HTTP.

What is not here

  • No credential chain discovery beyond the environment variable and an explicit file
  • No impersonation, workload identity federation, or external account files
  • No per-service endpoint resolution; a service package builds its own URL
  • No retry policy, which is per-service
  • No request model or serialization, which stays in the service package

Testing

go test ./cloud/google/
go test -tags force_tinygo_logic ./cloud/google/

The tests carry no build tag, so the signing path runs against crypto/rsa on the first command and against the OS backend on the second. A token that only mints correctly on one of them fails.

The self-signed JWT is checked claim by claim and then verified through the jwt verifier, so a signature that is well formed but wrong cannot pass on shape alone. What that does not cover is whether Google accepts it: the Datastore emulator ignores Authorization entirely, so the token path needs one manual run against a real endpoint.

Documentation

Overview

Package google holds what every Google Cloud client in this repository needs: credentials, bearer tokens, and the HTTP client the build selects.

It is the counterpart of cloud/aws, and it is shaped by the one real difference between the two clouds. AWS signs each request locally and the signature never leaves the process. Google wants a bearer token, and the ordinary way to get one is a POST to a second host — which on this stack means a second TLS handshake to a second pool entry before the first real call can go out.

So the default here is a self-signed JWT: sign a claim set with the service account key, send it as the bearer value, and skip the token endpoint entirely. Google documents this for Cloud APIs, with the service host as the audience.

creds, _ := google.CredentialsFromEnv()
ts, _ := google.JWTTokenSource(creds, "https://datastore.googleapis.com/")
token, _ := google.Cached(ts).Token(ctx)
req.Header.Set("Authorization", "Bearer "+token.Value)

The RSA operation itself lives in internal/rsasign, which uses crypto/rsa on host Go and the OS crypto library on TinyGo builds. That split is about binary size: crypto/rsa plus crypto/x509 cost about 588 KB, on targets where the whole HTTPS client is 272 KB.

Only JWTTokenSource and OAuth2TokenSource reference the signing code, so a binary built with StaticTokenSource or MetadataTokenSource drops it.

Credentials come from a service account file or from an explicitly supplied token. There is no credential chain discovery beyond the environment variable, no impersonation, and no workload identity federation.

One failure mode has no AWS equivalent and is worth stating: a self-signed JWT is only valid against the server's clock. A device with a wrong clock mints a token the server rejects as expired, reported as UNAUTHENTICATED, which is not a retryable error. The likeliest cause of that status in the field is the clock, not the key.

Index

Constants

View Source
const Backend = cloudhttp.Backend

Backend identifies the HTTP stack selected by build constraints.

Variables

View Source
var (
	ErrNoCredentials = errors.New("google: no credentials configured")
	ErrNoProject     = errors.New("google: no project configured")
	ErrBadPrivateKey = errors.New("google: private key is not a usable PKCS#8 RSA key")
	ErrTokenExpired  = errors.New("google: token expired")
)

Configuration failures shared by every service client. A rejected token is a wire response and belongs to the service package that decoded it.

Functions

func CloseIdleConnections

func CloseIdleConnections(client *http.Client)

CloseIdleConnections releases the pooled connections of client, if its transport keeps any. Both https.Transport and net/http.Transport do.

It exists because a service client should be closable without knowing which transport it was given, including one the caller supplied.

func EmulatorHost

func EmulatorHost(service string) string

EmulatorHost returns the host:port an emulator for service is listening on, or the empty string. The variable name is the one the gcloud emulators set, so "datastore" reads DATASTORE_EMULATOR_HOST.

The value carries no scheme. Emulators speak plain HTTP.

func NewHTTPClient

func NewHTTPClient(opts ClientOptions) *http.Client

NewHTTPClient builds the default HTTP client for a service package. The idle-connection setting is forwarded to https.Transport on TinyGo builds and to net/http.Transport otherwise, so it means the same thing on both paths.

func ProjectIDFromEnv

func ProjectIDFromEnv() string

ProjectIDFromEnv reads GOOGLE_CLOUD_PROJECT, then DATASTORE_PROJECT_ID, the latter being what the Datastore emulator sets.

func SignerBackend

func SignerBackend() string

SignerBackend names the RSA implementation this build selected. Backend, without the prefix, is the HTTP stack, matching cloud/aws.

Types

type CachedSource

type CachedSource struct {

	// Now is the clock; nil means time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

CachedSource holds one token and replaces it shortly before it expires.

func Cached

func Cached(source TokenSource) *CachedSource

Cached wraps a source so a token is fetched once per lifetime rather than once per request.

The refresh happens on the calling goroutine, under a mutex: one refresh is in flight and the others wait for it. There is no background refresher, both because it would outlive the client unless something stopped it and because TinyGo goroutines are not OS threads, so a goroutine cannot be relied on to make progress while another blocks in a socket call.

func (*CachedSource) Invalidate

func (c *CachedSource) Invalidate()

Invalidate drops the cached token, so the next Token call fetches a new one.

A service client calls this when the server rejects a token it believed was current, which happens when the two disagree about the time.

func (*CachedSource) Token

func (c *CachedSource) Token(ctx context.Context) (Token, error)

Token returns the cached token, refreshing it if it is missing or nearly expired.

type ClientOptions

type ClientOptions = cloudhttp.ClientOptions

ClientOptions configures the HTTP client a service package uses when the caller supplies none.

type Credentials

type Credentials struct {
	Type         string `json:"type"`
	ProjectID    string `json:"project_id"`
	PrivateKeyID string `json:"private_key_id"`
	PrivateKey   string `json:"private_key"`
	ClientEmail  string `json:"client_email"`
	TokenURI     string `json:"token_uri"`
}

Credentials are the fields of a service account key file that this package uses. The file carries more; the rest is not needed to mint a token.

func CredentialsFromEnv

func CredentialsFromEnv() (Credentials, error)

CredentialsFromEnv reads the file named by GOOGLE_APPLICATION_CREDENTIALS.

That is the whole of the resolution. The full Application Default Credentials search also consults a well-known gcloud config path, the GCE metadata server, and external account files; each is a different credential kind with a different failure mode, and a client this size is better off being told which one it has. MetadataTokenSource covers the GCE case explicitly.

func CredentialsFromFile

func CredentialsFromFile(path string) (Credentials, error)

CredentialsFromFile reads a service account key file from disk.

func CredentialsFromJSON

func CredentialsFromJSON(b []byte) (Credentials, error)

CredentialsFromJSON reads a service account key file.

func (Credentials) Valid

func (c Credentials) Valid() bool

Valid reports whether the fields needed to sign are present.

type JWTSource

type JWTSource struct {

	// Now is the clock. Nil means time.Now. A self-signed JWT is only valid
	// against the server's clock, so this is the one knob that matters when a
	// device disagrees with Google about what time it is.
	Now func() time.Time
	// contains filtered or unexported fields
}

JWTSource mints self-signed JWTs, the default credential path.

There is no token endpoint on this path: the JWT is the bearer value. That removes a second host from the connection pool and a round trip from the first call, which is the whole reason it is the default.

func JWTTokenSource

func JWTTokenSource(c Credentials, audience string) (*JWTSource, error)

JWTTokenSource builds a self-signed JWT source for one audience.

The audience is the service host with a trailing slash, for example "https://datastore.googleapis.com/". It is per-service, so a second Google service needs its own source; a token minted for one audience is not accepted by another.

func (*JWTSource) Close

func (s *JWTSource) Close() error

Close releases the signing key.

func (*JWTSource) Token

func (s *JWTSource) Token(ctx context.Context) (Token, error)

Token mints a JWT. It signs on every call; wrap with Cached to sign once per token lifetime instead.

type MetadataSource

type MetadataSource struct {
	// HTTPClient is the client used for the lookup. Nil means
	// http.DefaultClient.
	HTTPClient *http.Client

	// URL overrides the metadata endpoint. Empty means the standard one.
	URL string

	// Now is the clock; nil means time.Now.
	Now func() time.Time
}

MetadataSource reads a token from the GCE metadata server.

It needs no key and links no signing code, which makes it the cheapest path by binary size. It only works where the metadata server exists: GCE, Cloud Run, GKE.

func MetadataTokenSource

func MetadataTokenSource() *MetadataSource

MetadataTokenSource returns a source backed by the metadata server.

func (*MetadataSource) Token

func (s *MetadataSource) Token(ctx context.Context) (Token, error)

Token fetches an access token.

type OAuth2Source

type OAuth2Source struct {

	// HTTPClient is the client used for the exchange. Nil means
	// http.DefaultClient.
	HTTPClient *http.Client

	// Now is the clock; nil means time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

OAuth2Source exchanges a signed assertion for an access token at the token endpoint.

It costs a second host in the connection pool and a round trip before the first real call. JWTSource avoids both, so this exists for deployments that require a real access token rather than as the ordinary path.

func OAuth2TokenSource

func OAuth2TokenSource(c Credentials, scopes ...string) (*OAuth2Source, error)

OAuth2TokenSource builds an access-token source for the given scopes.

func (*OAuth2Source) Token

func (s *OAuth2Source) Token(ctx context.Context) (Token, error)

Token signs an assertion and exchanges it.

type RSASigner

type RSASigner struct {
	// contains filtered or unexported fields
}

RSASigner signs JWTs with a service account key. It implements jwt.Signer, which is the seam that keeps the jwt package free of build tags and cgo: the RSA operation happens in internal/rsasign, which is crypto/rsa on host Go and the OS crypto library on TinyGo builds.

It holds a native key handle on some builds, so Close is not optional.

func NewRSASigner

func NewRSASigner(c Credentials) (*RSASigner, error)

NewRSASigner parses the PKCS#8 private key in c.

func (*RSASigner) Algorithm

func (s *RSASigner) Algorithm() string

Algorithm reports RS256, the only algorithm Google service account keys use.

func (*RSASigner) Close

func (s *RSASigner) Close() error

Close releases the key. It is safe to call more than once.

func (*RSASigner) Sign

func (s *RSASigner) Sign(signingInput []byte) ([]byte, error)

Sign hashes the JWS signing input and signs the digest.

type Token

type Token struct {
	Value  string
	Expiry time.Time
}

Token is a bearer credential and the instant it stops being one.

func (Token) Valid

func (t Token) Valid(now time.Time) bool

Valid reports whether t is usable at the given time, allowing for the refresh margin.

type TokenSource

type TokenSource interface {
	Token(ctx context.Context) (Token, error)
}

TokenSource produces bearer tokens.

func StaticTokenSource

func StaticTokenSource(t Token) TokenSource

StaticTokenSource returns a source that always yields t.

This is the escape hatch for a device provisioned by a companion service, and the only path that links no signing code at all: a binary using it contains neither crypto/rsa nor the native RSA backend.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL