Ctrl K
ComunidadGuías…

internal/kafka/base.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package kafka

import (
	"context"
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
	"hash"

	"time"

	"github.com/Shopify/sarama"
	"github.com/flexprice/flexprice/internal/config"
	"github.com/xdg-go/scram"
)

// GetSaramaConfig builds a Sarama config for a single Kafka cluster. It takes the
// cluster's KafkaConfig directly (rather than the whole Configuration) so the same
// builder serves both the local cluster (cfg.Kafka) and the optional second cluster
// (cfg.KafkaSecondary) during the AWS→GCP migration — see infrastructure/docs/GCP-CUTOVER-STEPWISE.md.
// It returns an error rather than panicking on unusable TLS/SASL material (bad
// CA path, non-PEM bundle, missing GCP credentials) so a misconfigured deploy
// surfaces as a normal startup failure instead of a stack trace.
func GetSaramaConfig(kafkaCfg *config.KafkaConfig) (*sarama.Config, error) {
	saramaConfig := sarama.NewConfig()
	saramaConfig.Version = sarama.V2_1_0_0

	// Configure client ID regardless of SASL
	saramaConfig.ClientID = kafkaCfg.ClientID

	// Set consumer offset reset policy to ensure we don't miss messages
	// "earliest" ensures that when a consumer starts with no initial offset or
	// current offset is out of range, it will start from the earliest message
	saramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest

	// Enable auto commit to ensure offsets are committed regularly
	saramaConfig.Consumer.Offsets.AutoCommit.Enable = true
	saramaConfig.Consumer.Offsets.AutoCommit.Interval = 5000 * time.Millisecond // 5 seconds
	// A non-zero retention makes Sarama use OffsetCommit v2; zero defers retention
	// to the broker and retains Sarama's legacy OffsetCommit behavior.
	saramaConfig.Consumer.Offsets.Retention = kafkaCfg.OffsetRetention

	// When rebalancing happens, use the last committed offset
	saramaConfig.Consumer.Offsets.Retry.Max = 3

	// SASL implies TLS: SASL_SSL is the only SASL protocol this app speaks, so
	// enabling SASL turns TLS on even when kafkaCfg.TLS is false.
	if kafkaCfg.TLS || kafkaCfg.UseSASL {
		tlsConfig, err := BuildTLSConfig(kafkaCfg)
		if err != nil {
			return nil, err
		}
		saramaConfig.Net.TLS.Enable = true
		saramaConfig.Net.TLS.Config = tlsConfig
	}

	if !kafkaCfg.UseSASL {
		return saramaConfig, nil
	}

	// SASL specific configs
	saramaConfig.Net.SASL.Enable = true

	// sasl configs
	saramaConfig.Net.SASL.Mechanism = kafkaCfg.SASLMechanism

	switch kafkaCfg.SASLMechanism {
	case sarama.SASLTypeOAuth:
		// OAUTHBEARER (e.g. GCP Managed Kafka). Token comes from Application
		// Default Credentials — Workload Identity on GKE, gcloud locally.
		// User/Password are not used.
		provider, err := newGCPTokenProvider(context.Background(), kafkaCfg.SASLOAuthScopes)
		if err != nil {
			return nil, fmt.Errorf("kafka oauthbearer: init token provider (scopes=%v) — check GCP Application Default Credentials: %w", kafkaCfg.SASLOAuthScopes, err)
		}
		saramaConfig.Net.SASL.TokenProvider = provider
	case sarama.SASLTypeSCRAMSHA256, sarama.SASLTypeSCRAMSHA512:
		saramaConfig.Net.SASL.User = kafkaCfg.SASLUser
		saramaConfig.Net.SASL.Password = kafkaCfg.SASLPassword
		saramaConfig.Net.SASL.SCRAMClientGeneratorFunc = func() sarama.SCRAMClient {
			return &XDGSCRAMClient{HashGeneratorFcn: getHashGenerator(kafkaCfg.SASLMechanism)}
		}
	default:
		// PLAIN and any other mechanism that uses user+password.
		saramaConfig.Net.SASL.User = kafkaCfg.SASLUser
		saramaConfig.Net.SASL.Password = kafkaCfg.SASLPassword
	}

	return saramaConfig, nil
}

// XDGSCRAMClient implements sarama.SCRAMClient for SCRAM authentication
type XDGSCRAMClient struct {
	*scram.ClientConversation
	scram.HashGeneratorFcn
}

func (x *XDGSCRAMClient) Begin(userName, password, authzID string) (err error) {
	client, err := x.HashGeneratorFcn.NewClient(userName, password, authzID)
	if err != nil {
		return err
	}
	x.ClientConversation = client.NewConversation()
	return nil
}

func (x *XDGSCRAMClient) Step(challenge string) (response string, err error) {
	response, err = x.ClientConversation.Step(challenge)
	return
}

func (x *XDGSCRAMClient) Done() bool {
	return x.ClientConversation.Done()
}

// getHashGenerator returns the appropriate hash generator for the SASL mechanism
func getHashGenerator(mechanism sarama.SASLMechanism) scram.HashGeneratorFcn {
	switch mechanism {
	case sarama.SASLTypeSCRAMSHA512:
		return func() hash.Hash { return sha512.New() }
	case sarama.SASLTypeSCRAMSHA256:
		return func() hash.Hash { return sha256.New() }
	default:
		return func() hash.Hash { return sha512.New() }
	}
}