package worker import ( "context" "errors" "fmt" "strings" "google.golang.org/grpc/credentials" "google.golang.org/grpc/peer" ) var ErrUnauthorizedIdentity = errors.New("control-plane identity is not authorized") // AllowLoopbackIdentity is used only by the validated loopback plaintext mode. // It deliberately does not inspect transport credentials because that mode has no TLS peer. type AllowLoopbackIdentity struct{} func (AllowLoopbackIdentity) Authorize(context.Context, string) error { return nil } func (AllowLoopbackIdentity) AuthorizeChecker(context.Context, string) error { return nil } type SPIFFEIdentityAuthorizer struct { trustDomain string environment string } func NewSPIFFEIdentityAuthorizer(trustDomain, environment string) (*SPIFFEIdentityAuthorizer, error) { if strings.TrimSpace(trustDomain) == "" || strings.TrimSpace(environment) == "" { return nil, fmt.Errorf("spiffe identity authorizer: trust domain and environment are required") } return &SPIFFEIdentityAuthorizer{trustDomain: trustDomain, environment: environment}, nil } func (authorizer *SPIFFEIdentityAuthorizer) Authorize(ctx context.Context, workerID string) error { return authorizer.authorize(ctx, "worker", workerID) } func (authorizer *SPIFFEIdentityAuthorizer) AuthorizeChecker(ctx context.Context, checkerID string) error { return authorizer.authorize(ctx, "checker", checkerID) } func (authorizer *SPIFFEIdentityAuthorizer) authorize(ctx context.Context, resourceType, resourceID string) error { if authorizer == nil || (resourceType != "worker" && resourceType != "checker") || resourceID == "" { return ErrUnauthorizedIdentity } peerInfo, ok := peer.FromContext(ctx) if !ok || peerInfo.AuthInfo == nil { return ErrUnauthorizedIdentity } tlsInfo, ok := peerInfo.AuthInfo.(credentials.TLSInfo) if !ok || len(tlsInfo.State.VerifiedChains) == 0 { return ErrUnauthorizedIdentity } expectedPath := "/" + authorizer.environment + "/" + resourceType + "/" + resourceID identityCount := 0 for _, chain := range tlsInfo.State.VerifiedChains { if len(chain) == 0 || chain[0] == nil { continue } for _, uri := range chain[0].URIs { if uri == nil || uri.Scheme != "spiffe" || uri.Host != authorizer.trustDomain { continue } segments := strings.Split(strings.Trim(uri.Path, "/"), "/") if len(segments) != 3 || segments[1] != resourceType { continue } identityCount++ if identityCount > 1 || uri.RawQuery != "" || uri.Fragment != "" || uri.Path != expectedPath { return ErrUnauthorizedIdentity } } } if identityCount != 1 { return ErrUnauthorizedIdentity } return nil }