grpc/grpc-go · error

failed to establish stream to ALTS handshaker service: %v

Error message

failed to establish stream to ALTS handshaker service: %v

What it means

In altsHandshaker.ClientHandshake (handshaker.go:170-174), the client opens a bidirectional streaming RPC (DoHandshake) to the ALTS handshaker service running on GCP via h.clientConn. If that gRPC stream cannot be established, the error is wrapped as "failed to establish stream to ALTS handshaker service". The clientConn is the one returned by service.Dial(hsAddress).

Source

Thrown at credentials/alts/internal/handshaker/handshaker.go:173

// ClientHandshake starts and completes a client ALTS handshake for GCP. Once
// done, ClientHandshake returns a secure connection.
func (h *altsHandshaker) ClientHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error) {
	if err := clientHandshakes.Acquire(ctx, 1); err != nil {
		return nil, nil, err
	}
	defer clientHandshakes.Release(1)

	if h.side != core.ClientSide {
		return nil, nil, errors.New("only handshakers created using NewClientHandshaker can perform a client handshaker")
	}

	// TODO(matthewstevenson88): Change unit tests to use public APIs so
	// that h.stream can unconditionally be set based on h.clientConn.
	if h.stream == nil {
		stream, err := altsgrpc.NewHandshakerServiceClient(h.clientConn).DoHandshake(ctx)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to establish stream to ALTS handshaker service: %v", err)
		}
		h.stream = stream
	}

	// Create target identities from service account list.
	targetIdentities := make([]*altspb.Identity, 0, len(h.clientOpts.TargetServiceAccounts))
	for _, account := range h.clientOpts.TargetServiceAccounts {
		targetIdentities = append(targetIdentities, &altspb.Identity{
			IdentityOneof: &altspb.Identity_ServiceAccount{
				ServiceAccount: account,
			},
		})
	}
	req := &altspb.HandshakerReq{
		ReqOneof: &altspb.HandshakerReq_ClientStart{
			ClientStart: &altspb.StartClientHandshakeReq{
				HandshakeSecurityProtocol: hsProtocol,
				ApplicationProtocols:      appProtocols,

View on GitHub (pinned to 03255a9237)

Solutions

  1. Confirm the workload is actually on GCP and the ALTS handshaker service is reachable (ErrUntrustedPlatform is a related earlier guard).
  2. Check the inner %v for the stream RPC error (UNAVAILABLE, DEADLINE_EXCEEDED, PERMISSION_DENIED) and address it.
  3. If not on GCP, switch to TLS credentials instead of ALTS.

Example fix

// before
creds, _ := alts.NewClientCreds(...)
conn, _ := grpc.NewClient(target, grpc.WithTransportCredentials(creds))  // off GCP
// after (off GCP)
creds := credentials.NewTLS(&tls.Config{ServerName: target})
conn, _ := grpc.NewClient(target, grpc.WithTransportCredentials(creds))
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm you are on GCP before attempting ALTS; otherwise use TLS.
func onGCP() bool { _, err := metadata.Get("instance/zone"); return err == nil }

Try / catch

secConn, authInfo, err := chs.ClientHandshake(ctx)
if err != nil {
    if strings.Contains(err.Error(), "failed to establish stream to ALTS handshaker service") {
        log.Printf("ALTS handshaker service unreachable: %v; falling back to TLS", err)
        // fall back to a TLS credentials handshake instead
    }
    return nil, nil, err
}

Prevention

When it happens

Trigger: Performing an ALTS client handshake when the connection to the local ALTS handshaker service (the GCP ALTS-MDB endpoint) cannot be opened — network error, service unavailable, deadline, or auth failure talking to the handshaker service.

Common situations: Running ALTS outside GCP (where the handshaker service is absent); GCP metadata server / ALTS daemon not reachable; firewall blocking the handshaker service port; transient GCP control-plane issue; the hsAddress is misconfigured.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/1a072e1cf4ad75c5. Report an issue: GitHub.