85 lines
2.6 KiB
Go
85 lines
2.6 KiB
Go
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
|
|
}
|