grpc/grpc-go · error

credentials: cannot send secure credentials on an insecure c

Error message

credentials: cannot send secure credentials on an insecure connection: %v

What it means

In GetRequestMetadata the credential calls CheckSecurityLevel(ri.AuthInfo, PrivacyAndIntegrity) and wraps any failure (gcp_service_account_identity_credentials.go:129-131). Because RequireTransportSecurity() returns true, sending the bearer token over anything below TLS/ALTS is refused. The inner %v is typically the [165] 'requires SecurityLevel' message.

Source

Thrown at credentials/google/gcp_service_account_identity_credentials.go:130

		ctx:      ctx,
		audience: audience,
		creds:    creds,
		backoff:  internal.BackoffStrategy,
	}, nil
}

// GetRequestMetadata gets the current request metadata, refreshing tokens if
// required. This implementation follows the PerRPCCredentials interface.
//
// It guarantees that only one underlying token fetch will be executed
// concurrently. If a valid token is cached, it is returned immediately. If
// a fetch recently failed, the cached error is returned until the backoff
// interval expires. Otherwise, it initiates a new token fetch or blocks
// waiting for an already-in-progress fetch to complete.
func (c *gcpServiceAccountIdentityCallCreds) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
	ri, _ := credentials.RequestInfoFromContext(ctx)
	if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
		return nil, fmt.Errorf("credentials: cannot send secure credentials on an insecure connection: %v", err)
	}

	if md, err := c.cachedRequestMetadata(true); md != nil || err != nil {
		return md, err
	}

	c.mu.Lock()
	// Now that we have the lock, did someone else finish the fetch while we
	// were waiting for the lock?
	md, err := c.cachedRequestMetadataLocked(false)
	if md != nil || err != nil {
		c.mu.Unlock()
		return md, err
	}

	// If no one is fetching, start it.
	if c.fetching == nil {
		c.fetching = make(chan struct{})

View on GitHub (pinned to 03255a9237)

Solutions

  1. Use TLS or ALTS transport credentials on the channel so the connection is PrivacyAndIntegrity.
  2. Do not attach these credentials to an insecure dial; drop grpc.WithPerRPCCredentials for plaintext testing.
  3. Verify RequireTransportSecurity()==true is honored by your channel construction (gRPC refuses insecure+secure-creds only if you let it).
  4. Confirm the transport credentials' AuthInfo sets SecurityLevel correctly (see [165]).

Example fix

// before
conn, _ := grpc.NewClient(addr,
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithPerRPCCreds(grpcCredentials), // triggers insecure-connection error
)

// after
conn, _ := grpc.NewClient(addr,
    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ServerName: addr})),
    grpc.WithPerRPCCreds(grpcCredentials),
)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the channel is TLS/ALTS before attaching these creds.
if rpcCreds.RequireTransportSecurity() && transportIsInsecure(dialOpts) {
    return errors.New("cannot use secure per-RPC creds over insecure transport")
}

Try / catch

md, err := rpcCreds.GetRequestMetadata(ctx)
if err != nil && strings.Contains(err.Error(), "insecure connection") {
    // configuration error: switch the channel to TLS/ALTS.
    return nil, fmt.Errorf("channel transport must be secured: %w", err)
}

Prevention

When it happens

Trigger: Dialing a gRPC server with grpc.WithTransportCredentials(insecure.NewCredentials()) (or an integrity-only transport) while attaching these per-RPC credentials. The first RPC triggers GetRequestMetadata, which sees NoSecurity AuthInfo and aborts before sending the token.

Common situations: Dev/staging channel configured plaintext 'just to test', forgetting to flip insecure→TLS when promoting; a custom transport whose AuthInfo.SecurityLevel defaults to zero; or a misconfigured ALTS bundle that fell back to plaintext.

Related errors


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