OWASP Cheat Sheet Series: gRPC Security Cheat Sheet

# gRPC Security Cheat Sheet

## Introduction

gRPC (gRPC Remote Procedure Call) is a high-performance, language-neutral RPC framework that uses HTTP/2 for transport and Protocol Buffers for serialization. While gRPC offers significant performance advantages for microservices and distributed systems, it introduces unique security challenges that differ from traditional REST APIs.

The following sections cover essential security controls for protecting gRPC services from common attack vectors.

## Transport Security

### Always Use TLS in Production

Production deployments need TLS encryption to protect against eavesdropping and man-in-the-middle attacks.

```go
// Go - Secure server with TLS
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
if err != nil {
    log.Fatalf("Failed to load TLS credentials: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))
```

Configure TLS 1.2 or higher with strong cipher suites, and disable weak protocols and ciphers.

### Implement Mutual TLS (mTLS) for Service-to-Service Communication

mTLS provides mutual authentication where both client and server verify each other's certificates, enabling zero-trust communication.

```go
// Go - mTLS client configuration
cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
caCert, err := ioutil.ReadFile(caCertFile)
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

creds := credentials.NewTLS(&tls.Config{
    Certificates: []tls.Certificate{cert},
    RootCAs:      caCertPool,
})
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
```

Use short-lived certificates (90 days or less) with automated rotation to limit the impact of compromised keys.

## Authentication and Authorization

### Implement Strong Authentication

Implement authentication checks for each protected service method.

#### Token-Based Authentication

```go
// Go - JWT token validation interceptor
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
    }
    
    tokens := md["authorization"]
    if len(tokens) == 0 {
        return nil, status.Errorf(codes.Unauthenticated, "missing authorization token")
    }
    
    token := strings.TrimPrefix(tokens[0], "Bearer ")
    if !validateJWT(token) {
        return nil, status.Errorf(codes.Unauthenticated, "invalid token")
    }
    
    return handler(ctx, req)
}
```

#### API Key Authentication

```go
// Go - API key validation
func validateAPIKey(ctx context.Context) error {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return status.Error(codes.Unauthenticated, "missing metadata")
    }
    
    keys := md["x-api-key"]
    if len(keys) == 0 || !isValidAPIKey(keys[0]) {
        return status.Error(codes.Unauthenticated, "invalid API key")
    }
    return nil
}
```

Implement token expiration and refresh mechanisms with short-lived tokens (15-60 minutes). Avoid embedding credentials in gRPC method parameters - use metadata headers.

### Enforce Granular Authorization

Implement method-level authorization checks based on the principle of least privilege.

```go
// Go - Role-based authorization
func authorizeMethod(ctx context.Context, methodName string, userRoles []string) error {
    requiredRole, exists := methodPermissions[methodName]
    if !exists {
        return status.Errorf(codes.PermissionDenied, "method not found")
    }
    
    for _, role := range userRoles {
        if role == requiredRole {
            return nil
        }
    }
    
    return status.Errorf(codes.PermissionDenied, "insufficient permissions")
}
```

Log all authorization failures to detect potential attacks and compliance violations.

## Input Validation and Data Security

### Validate All Protocol Buffer Messages

Protocol Buffers provide type safety but not business logic validation. Always perform thorough server-side validation.

```protobuf
// Use protoc-gen-validate for automatic validation
syntax = "proto3";
import "validate/validate.proto";

message CreateUserRequest {
  string email = 1 [(validate.rules).string.email = true];
  string name = 2 [(validate.rules).string = {min_len: 1, max_len: 100}];
  int32 age = 3 [(validate.rules).int32 = {gte: 0, lte: 150}];
}
```

Use allowlist validation for string inputs to prevent unexpected characters and injection attempts.

### Prevent Injection Attacks

Validate user input carefully when used in database queries or system operations.

```go
// Go - Safe database query with parameterization
func getUserByEmail(email string) (*User, error) {
    if !isValidEmail(email) {
        return nil, errors.New("invalid email format")
    }
    
    query := "SELECT id, name, email FROM users WHERE email = ?"
    row := db.QueryRow(query, email)
    
    var user User
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    return &user, err
}
```

Always use prepared statements for database operations to prevent [SQL injection](SQL_Injection_Prevention_Cheat_Sheet.md).

### Implement Message Size Limits

gRPC's streaming capabilities allow clients to send arbitrarily large messages, potentially exhausting server memory and triggering denial-of-service conditions. Set clear limits on message sizes.

```go
// Go - Set message size limits
s := grpc.NewServer(
    grpc.MaxRecvMsgSize(4*1024*1024), // 4MB max receive
    grpc.MaxSendMsgSize(4*1024*1024), // 4MB max send
)
```

Limit streaming sessions and message counts to prevent resource exhaustion. Monitor and enforce maximum messages per stream and maximum session duration.

## Rate Limiting and Resource Protection

### Implement Request Rate Limiting

Protect services from request flooding and resource exhaustion.

```go
// Go - Rate limiting with memory management
import (
    "golang.org/x/time/rate"
    "sync"
    "time"
)

type RateLimiterStore struct {
    limiters map[string]*rateLimiterEntry
    mu       sync.RWMutex
}

type rateLimiterEntry struct {
    limiter  *rate.Limiter
    lastSeen time.Time
}

var store = &RateLimiterStore{
    limiters: make(map[string]*rateLimiterEntry),
}

func rateLimitInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    clientIP := getClientIP(ctx)
    
    store.mu.Lock()
    entry, exists := store.limiters[clientIP]
    if !exists {
        entry = &rateLimiterEntry{
            limiter:  rate.NewLimiter(rate.Limit(10), 20), // 10 req/sec, burst 20
            lastSeen: time.Now(),
        }
        store.limiters[clientIP] = entry
    }
    entry.lastSeen = time.Now()
    store.mu.Unlock()
    
    if !entry.limiter.Allow() {
        return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
    }
    
    return handler(ctx, req)
}

// Cleanup old limiters periodicall