github.com/blend/go-sdk@v1.20220411.3/envoyutil/identity_processor.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package envoyutil
     9  
    10  import (
    11  	"fmt"
    12  	"strings"
    13  
    14  	"github.com/blend/go-sdk/collections"
    15  	"github.com/blend/go-sdk/ex"
    16  	"github.com/blend/go-sdk/spiffeutil"
    17  )
    18  
    19  // NOTE: Ensure that
    20  //       - `IdentityProcessor.KubernetesIdentityFormatter` satisfies `IdentityFormatter`
    21  //       - `IdentityProcessor.IdentityProvider` satisfies `IdentityProvider`
    22  var (
    23  	_ IdentityFormatter = IdentityProcessor{}.KubernetesIdentityFormatter
    24  	_ IdentityProvider  = IdentityProcessor{}.IdentityProvider
    25  )
    26  
    27  // IdentityProcessorOption mutates an identity processor.
    28  type IdentityProcessorOption func(*IdentityProcessor)
    29  
    30  // OptIdentityType sets the identity type for the processor.
    31  func OptIdentityType(it IdentityType) IdentityProcessorOption {
    32  	return func(ip *IdentityProcessor) {
    33  		ip.Type = it
    34  	}
    35  }
    36  
    37  // OptAllowedTrustDomains adds allowed trust domains to the processor.
    38  func OptAllowedTrustDomains(trustDomains ...string) IdentityProcessorOption {
    39  	return func(ip *IdentityProcessor) {
    40  		ip.AllowedTrustDomains = append(ip.AllowedTrustDomains, trustDomains...)
    41  	}
    42  }
    43  
    44  // OptDeniedTrustDomains adds denied trust domains to the processor.
    45  func OptDeniedTrustDomains(trustDomains ...string) IdentityProcessorOption {
    46  	return func(ip *IdentityProcessor) {
    47  		ip.DeniedTrustDomains = append(ip.DeniedTrustDomains, trustDomains...)
    48  	}
    49  }
    50  
    51  // OptAllowedIdentities adds allowed identities to the processor.
    52  func OptAllowedIdentities(identities ...string) IdentityProcessorOption {
    53  	return func(ip *IdentityProcessor) {
    54  		ip.AllowedIdentities = ip.AllowedIdentities.Union(
    55  			collections.NewSetOfString(identities...),
    56  		)
    57  	}
    58  }
    59  
    60  // OptDeniedIdentities adds denied identities to the processor.
    61  func OptDeniedIdentities(identities ...string) IdentityProcessorOption {
    62  	return func(ip *IdentityProcessor) {
    63  		ip.DeniedIdentities = ip.DeniedIdentities.Union(
    64  			collections.NewSetOfString(identities...),
    65  		)
    66  	}
    67  }
    68  
    69  // OptFormatIdentity sets the `FormatIdentity` on the processor.
    70  func OptFormatIdentity(formatter IdentityFormatter) IdentityProcessorOption {
    71  	return func(ip *IdentityProcessor) {
    72  		ip.FormatIdentity = formatter
    73  	}
    74  }
    75  
    76  // IdentityFormatter describes functions that will produce an identity string
    77  // from a parsed SPIFFE URI.
    78  type IdentityFormatter = func(XFCCElement, *spiffeutil.ParsedURI) (string, error)
    79  
    80  // IdentityType represents the type of identity that will be extracted by an
    81  // `IdentityProcessor`. It can either be a client or server identity.
    82  type IdentityType int
    83  
    84  const (
    85  	// ClientIdentity represents client identity.
    86  	ClientIdentity IdentityType = 0
    87  	// ServerIdentity represents server identity.
    88  	ServerIdentity IdentityType = 1
    89  )
    90  
    91  // IdentityProcessor provides configurable fields that can be used to
    92  // help validate a parsed SPIFFE URI and produce and validate an identity from
    93  // a parsed SPIFFE URI. The `Type` field determines if a client or server
    94  // identity should be provided; by default the type will be client identity.
    95  type IdentityProcessor struct {
    96  	Type                IdentityType
    97  	AllowedTrustDomains []string
    98  	DeniedTrustDomains  []string
    99  	AllowedIdentities   collections.SetOfString
   100  	DeniedIdentities    collections.SetOfString
   101  	FormatIdentity      IdentityFormatter
   102  }
   103  
   104  // IdentityProvider returns a client or server identity; it uses the configured
   105  // rules to validate and format the identity by parsing the `URI` field (for
   106  // client identity) or `By` field (for server identity) of the XFCC element. If
   107  // `FormatIdentity` has not been specified, the `KubernetesIdentityFormatter()`
   108  // method will be used as a fallback.
   109  //
   110  // This method satisfies the `IdentityProvider` interface.
   111  func (ip IdentityProcessor) IdentityProvider(xfcc XFCCElement) (string, error) {
   112  	uriValue := ip.getURIForIdentity(xfcc)
   113  
   114  	if uriValue == "" {
   115  		return "", &XFCCValidationError{
   116  			Class: ip.errInvalidIdentity(),
   117  			XFCC:  xfcc.String(),
   118  		}
   119  	}
   120  
   121  	pu, err := spiffeutil.Parse(uriValue)
   122  	// NOTE: The `pu == nil` check is redundant, we expect `spiffeutil.Parse()`
   123  	//       not to violate the invariant that `pu != nil` when `err == nil`.
   124  	if err != nil || pu == nil {
   125  		return "", &XFCCExtractionError{
   126  			Class: ip.errInvalidIdentity(),
   127  			XFCC:  xfcc.String(),
   128  		}
   129  	}
   130  
   131  	if err := ip.ProcessAllowedTrustDomains(xfcc, pu); err != nil {
   132  		return "", err
   133  	}
   134  	if err := ip.ProcessDeniedTrustDomains(xfcc, pu); err != nil {
   135  		return "", err
   136  	}
   137  
   138  	identity, err := ip.formatIdentity(xfcc, pu)
   139  	if err != nil {
   140  		return "", err
   141  	}
   142  
   143  	if err := ip.ProcessAllowedIdentities(xfcc, identity); err != nil {
   144  		return "", err
   145  	}
   146  	if err := ip.ProcessDeniedIdentities(xfcc, identity); err != nil {
   147  		return "", err
   148  	}
   149  	return identity, nil
   150  }
   151  
   152  // KubernetesIdentityFormatter assumes the SPIFFE URI contains a Kubernetes
   153  // workload ID of the form `ns/{namespace}/sa/{serviceAccount}` and formats the
   154  // identity as `{serviceAccount}.{namespace}`. This function satisfies the
   155  // `IdentityFormatter` interface.
   156  func (ip IdentityProcessor) KubernetesIdentityFormatter(xfcc XFCCElement, pu *spiffeutil.ParsedURI) (string, error) {
   157  	kw, err := spiffeutil.ParseKubernetesWorkloadID(pu.WorkloadID)
   158  	if err != nil {
   159  		return "", &XFCCExtractionError{
   160  			Class: ip.errInvalidIdentity(),
   161  			XFCC:  xfcc.String(),
   162  		}
   163  	}
   164  	return fmt.Sprintf("%s.%s", kw.ServiceAccount, kw.Namespace), nil
   165  }
   166  
   167  // ProcessAllowedTrustDomains returns an error if an allow list is configured
   168  // and the trust domain from the parsed SPIFFE URI does not match any elements
   169  // in the list.
   170  func (ip IdentityProcessor) ProcessAllowedTrustDomains(xfcc XFCCElement, pu *spiffeutil.ParsedURI) error {
   171  	if len(ip.AllowedTrustDomains) == 0 {
   172  		return nil
   173  	}
   174  
   175  	for _, allowed := range ip.AllowedTrustDomains {
   176  		if strings.EqualFold(pu.TrustDomain, allowed) {
   177  			return nil
   178  		}
   179  	}
   180  	return &XFCCValidationError{
   181  		Class: ip.errInvalidIdentity(),
   182  		XFCC:  xfcc.String(),
   183  		Metadata: map[string]string{
   184  			"trustDomain": pu.TrustDomain,
   185  		},
   186  	}
   187  }
   188  
   189  // ProcessDeniedTrustDomains returns an error if a denied list is configured
   190  // and the trust domain from the parsed SPIFFE URI matches any elements in the
   191  // list.
   192  func (ip IdentityProcessor) ProcessDeniedTrustDomains(xfcc XFCCElement, pu *spiffeutil.ParsedURI) error {
   193  	for _, denied := range ip.DeniedTrustDomains {
   194  		if strings.EqualFold(pu.TrustDomain, denied) {
   195  			return &XFCCValidationError{
   196  				Class: ip.errInvalidIdentity(),
   197  				XFCC:  xfcc.String(),
   198  				Metadata: map[string]string{
   199  					"trustDomain": pu.TrustDomain,
   200  				},
   201  			}
   202  		}
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // ProcessAllowedIdentities returns an error if an allow list is configured
   209  // and the identity does not match any elements in the list.
   210  func (ip IdentityProcessor) ProcessAllowedIdentities(xfcc XFCCElement, identity string) error {
   211  	if ip.AllowedIdentities.Len() == 0 {
   212  		return nil
   213  	}
   214  
   215  	if ip.AllowedIdentities.Contains(identity) {
   216  		return nil
   217  	}
   218  
   219  	return &XFCCValidationError{
   220  		Class: ip.errDeniedIdentity(),
   221  		XFCC:  xfcc.String(),
   222  		Metadata: map[string]string{
   223  			ip.getIdentityKey(): identity,
   224  		},
   225  	}
   226  }
   227  
   228  // ProcessDeniedIdentities returns an error if a denied list is configured
   229  // and the identity matches any elements in the list.
   230  func (ip IdentityProcessor) ProcessDeniedIdentities(xfcc XFCCElement, identity string) error {
   231  	if ip.DeniedIdentities.Len() == 0 {
   232  		return nil
   233  	}
   234  
   235  	if ip.DeniedIdentities.Contains(identity) {
   236  		return &XFCCValidationError{
   237  			Class: ip.errDeniedIdentity(),
   238  			XFCC:  xfcc.String(),
   239  			Metadata: map[string]string{
   240  				ip.getIdentityKey(): identity,
   241  			},
   242  		}
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  // formatIdentity invokes the `FormatIdentity` on the current processor
   249  // or falls back to `KubernetesIdentityFormatter()` if it is not set.
   250  func (ip IdentityProcessor) formatIdentity(xfcc XFCCElement, pu *spiffeutil.ParsedURI) (string, error) {
   251  	if ip.FormatIdentity != nil {
   252  		return ip.FormatIdentity(xfcc, pu)
   253  	}
   254  	return ip.KubernetesIdentityFormatter(xfcc, pu)
   255  }
   256  
   257  // getURIForIdentity returns either the `URI` field if this processor has `Type`
   258  // "client identity" or the `By` field for the server identity.
   259  func (ip IdentityProcessor) getURIForIdentity(xfcc XFCCElement) string {
   260  	if ip.Type == ClientIdentity {
   261  		return xfcc.URI
   262  	}
   263  
   264  	return xfcc.By
   265  }
   266  
   267  // getIdentityKey returns a key to be used in error metadata indicating if a
   268  // value is client identity or server identity.
   269  func (ip IdentityProcessor) getIdentityKey() string {
   270  	if ip.Type == ClientIdentity {
   271  		return "clientIdentity"
   272  	}
   273  
   274  	return "serverIdentity"
   275  }
   276  
   277  // errInvalidIdentity maps the `Type` to a specific error class indicating
   278  // an invalid identity.
   279  func (ip IdentityProcessor) errInvalidIdentity() ex.Class {
   280  	if ip.Type == ClientIdentity {
   281  		return ErrInvalidClientIdentity
   282  	}
   283  
   284  	return ErrInvalidServerIdentity
   285  }
   286  
   287  // errDeniedIdentity maps the `Type` to a specific error class indicating
   288  // a denied identity.
   289  func (ip IdentityProcessor) errDeniedIdentity() ex.Class {
   290  	if ip.Type == ClientIdentity {
   291  		return ErrDeniedClientIdentity
   292  	}
   293  
   294  	return ErrDeniedServerIdentity
   295  }