github.com/blend/go-sdk@v1.20220411.3/envoyutil/identity_test.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_test
     9  
    10  import (
    11  	"fmt"
    12  	"net/http"
    13  	"testing"
    14  
    15  	sdkAssert "github.com/blend/go-sdk/assert"
    16  	"github.com/blend/go-sdk/ex"
    17  
    18  	"github.com/blend/go-sdk/envoyutil"
    19  )
    20  
    21  // NOTE: Ensure
    22  //       - `extractJustURI` satisfies `envoyutil.IdentityProvider`.
    23  //       - `extractFailure` satisfies `envoyutil.IdentityProvider`.
    24  var (
    25  	_ envoyutil.IdentityProvider = extractJustURI
    26  	_ envoyutil.IdentityProvider = extractFailure
    27  )
    28  
    29  func TestExtractAndVerifyClientIdentity(t *testing.T) {
    30  	assert := sdkAssert.New(t)
    31  
    32  	type testCase struct {
    33  		XFCC           string
    34  		ClientIdentity string
    35  		ErrorType      string
    36  		Class          ex.Class
    37  		Extract        envoyutil.IdentityProvider
    38  		Verifiers      []envoyutil.VerifyXFCC
    39  	}
    40  	testCases := []testCase{
    41  		{ErrorType: "XFCCFatalError", Class: envoyutil.ErrMissingExtractFunction},
    42  		{XFCC: "", ErrorType: "XFCCValidationError", Class: envoyutil.ErrMissingXFCC, Extract: extractJustURI},
    43  		{XFCC: `""`, ErrorType: "XFCCExtractionError", Class: envoyutil.ErrInvalidXFCC, Extract: extractJustURI},
    44  		{XFCC: "something=bad", ErrorType: "XFCCExtractionError", Class: envoyutil.ErrInvalidXFCC, Extract: extractJustURI},
    45  		{
    46  			XFCC:      "By=first,URI=second",
    47  			ErrorType: "XFCCValidationError",
    48  			Class:     envoyutil.ErrInvalidXFCC,
    49  			Extract:   extractJustURI,
    50  		},
    51  		{
    52  			XFCC:           "By=spiffe://cluster.local/ns/blend/sa/idea;URI=spiffe://cluster.local/ns/light/sa/bulb",
    53  			ClientIdentity: "spiffe://cluster.local/ns/light/sa/bulb",
    54  			Extract:        extractJustURI,
    55  		},
    56  		{XFCC: "By=x;URI=y", ErrorType: "XFCCExtractionError", Class: "extractFailure", Extract: extractFailure},
    57  		{
    58  			XFCC:      "By=abc;URI=def",
    59  			ErrorType: "XFCCValidationError",
    60  			Class:     `verifyFailure: expected "xyz"`,
    61  			Extract:   extractJustURI,
    62  			Verifiers: []envoyutil.VerifyXFCC{makeVerifyXFCC("xyz")},
    63  		},
    64  		{
    65  			XFCC:           "By=abc;URI=def",
    66  			ClientIdentity: "def",
    67  			Extract:        extractJustURI,
    68  			Verifiers:      []envoyutil.VerifyXFCC{makeVerifyXFCC("abc")},
    69  		},
    70  		{
    71  			XFCC:      "By=abc;URI=def",
    72  			ErrorType: "XFCCFatalError",
    73  			Class:     envoyutil.ErrVerifierNil,
    74  			Extract:   extractJustURI,
    75  			Verifiers: []envoyutil.VerifyXFCC{nil},
    76  		},
    77  	}
    78  
    79  	for _, tc := range testCases {
    80  		// Set-up mock context.
    81  		r, newReqErr := http.NewRequest("GET", "", nil)
    82  		assert.Nil(newReqErr)
    83  		if tc.XFCC != "" {
    84  			r.Header.Add(envoyutil.HeaderXFCC, tc.XFCC)
    85  		}
    86  
    87  		clientIdentity, err := envoyutil.ExtractAndVerifyClientIdentity(r, tc.Extract, tc.Verifiers...)
    88  		assert.Equal(tc.ClientIdentity, clientIdentity)
    89  		switch tc.ErrorType {
    90  		case "XFCCExtractionError":
    91  			assert.True(envoyutil.IsExtractionError(err), tc)
    92  			expected := &envoyutil.XFCCExtractionError{Class: tc.Class, XFCC: tc.XFCC}
    93  			assert.Equal(expected, err, tc)
    94  		case "XFCCValidationError":
    95  			assert.True(envoyutil.IsValidationError(err), tc)
    96  			expected := &envoyutil.XFCCValidationError{Class: tc.Class, XFCC: tc.XFCC}
    97  			assert.Equal(expected, err, tc)
    98  		case "XFCCFatalError":
    99  			assert.True(envoyutil.IsFatalError(err), tc)
   100  			expected := &envoyutil.XFCCFatalError{Class: tc.Class, XFCC: tc.XFCC}
   101  			assert.Equal(expected, err, tc)
   102  		default:
   103  			assert.Nil(err, tc)
   104  		}
   105  	}
   106  }
   107  
   108  func TestSPIFFEClientIdentityProvider(t *testing.T) {
   109  	assert := sdkAssert.New(t)
   110  
   111  	type testCase struct {
   112  		XFCC           string
   113  		TrustDomain    string
   114  		ClientIdentity string
   115  		ErrorType      string
   116  		Class          ex.Class
   117  		Metadata       interface{}
   118  		Denied         []string
   119  	}
   120  	testCases := []testCase{
   121  		{
   122  			XFCC:      "URI=not-spiffe",
   123  			ErrorType: "XFCCExtractionError",
   124  			Class:     envoyutil.ErrInvalidClientIdentity,
   125  		},
   126  		{
   127  			XFCC:           "URI=spiffe://cluster.local/ns/light1/sa/bulb",
   128  			TrustDomain:    "cluster.local",
   129  			ClientIdentity: "bulb.light1",
   130  		},
   131  		{
   132  			XFCC:        "URI=spiffe://cluster.local/ns/light2/sa/bulb",
   133  			TrustDomain: "k8s.local",
   134  			ErrorType:   "XFCCValidationError",
   135  			Class:       envoyutil.ErrInvalidClientIdentity,
   136  			Metadata:    map[string]string{"trustDomain": "cluster.local"},
   137  		},
   138  		{
   139  			XFCC:        "URI=spiffe://cluster.local/ns/light3/sa/bulb/extra",
   140  			TrustDomain: "cluster.local",
   141  			ErrorType:   "XFCCExtractionError",
   142  			Class:       envoyutil.ErrInvalidClientIdentity,
   143  		},
   144  		{
   145  			XFCC:        "URI=spiffe://cluster.local/ns/light4/sa/bulb",
   146  			TrustDomain: "cluster.local",
   147  			ErrorType:   "XFCCValidationError",
   148  			Class:       envoyutil.ErrDeniedClientIdentity,
   149  			Metadata:    map[string]string{"clientIdentity": "bulb.light4"},
   150  			Denied:      []string{"bulb.light4"},
   151  		},
   152  		{
   153  			XFCC:           "URI=spiffe://cluster.local/ns/light5/sa/bulb",
   154  			TrustDomain:    "cluster.local",
   155  			ClientIdentity: "bulb.light5",
   156  			Denied:         []string{"not.me"},
   157  		},
   158  		{
   159  			XFCC:        "URI=spiffe://cluster.local/ns/light6/sa/bulb",
   160  			TrustDomain: "cluster.local",
   161  			ErrorType:   "XFCCValidationError",
   162  			Class:       envoyutil.ErrDeniedClientIdentity,
   163  			Metadata:    map[string]string{"clientIdentity": "bulb.light6"},
   164  			Denied:      []string{"not.me", "bulb.light6", "also.not-me"},
   165  		},
   166  	}
   167  
   168  	for _, tc := range testCases {
   169  		xfccElements, err := envoyutil.ParseXFCC(tc.XFCC)
   170  		assert.Nil(err)
   171  		assert.Len(xfccElements, 1)
   172  		xfcc := xfccElements[0]
   173  
   174  		cip := envoyutil.SPIFFEClientIdentityProvider(
   175  			envoyutil.OptAllowedTrustDomains(tc.TrustDomain),
   176  			envoyutil.OptDeniedIdentities(tc.Denied...),
   177  		)
   178  		clientIdentity, err := cip(xfcc)
   179  		assert.Equal(tc.ClientIdentity, clientIdentity)
   180  
   181  		switch tc.ErrorType {
   182  		case "XFCCExtractionError":
   183  			assert.True(envoyutil.IsExtractionError(err), tc)
   184  			expected := &envoyutil.XFCCExtractionError{Class: tc.Class, XFCC: tc.XFCC, Metadata: tc.Metadata}
   185  			assert.Equal(expected, err, tc)
   186  		case "XFCCValidationError":
   187  			assert.True(envoyutil.IsValidationError(err), tc)
   188  			expected := &envoyutil.XFCCValidationError{Class: tc.Class, XFCC: tc.XFCC, Metadata: tc.Metadata}
   189  			assert.Equal(expected, err, tc)
   190  		default:
   191  			assert.Nil(err, tc)
   192  		}
   193  	}
   194  }
   195  
   196  func TestSPIFFEServerIdentityProvider(t *testing.T) {
   197  	assert := sdkAssert.New(t)
   198  
   199  	// Verifier returns `nil` error when server identity is valid.
   200  	verifier := envoyutil.SPIFFEServerIdentityProvider()
   201  	xfcc := envoyutil.XFCCElement{By: "spiffe://cluster.local/ns/time/sa/line"}
   202  	err := verifier(xfcc)
   203  	assert.Nil(err)
   204  
   205  	// Verifier returns extraction error when server identity is invalid.
   206  	verifier = envoyutil.SPIFFEServerIdentityProvider()
   207  	xfcc = envoyutil.XFCCElement{By: "not-spiffe"}
   208  	err = verifier(xfcc)
   209  	assert.True(envoyutil.IsExtractionError(err))
   210  	var expected error = &envoyutil.XFCCExtractionError{
   211  		Class: envoyutil.ErrInvalidServerIdentity,
   212  		XFCC:  xfcc.String(),
   213  	}
   214  	assert.Equal(expected, err)
   215  
   216  	// Verifier returns validation error when server identity is in deny list.
   217  	verifier = envoyutil.SPIFFEServerIdentityProvider(
   218  		envoyutil.OptDeniedIdentities("line.time"),
   219  	)
   220  	xfcc = envoyutil.XFCCElement{By: "spiffe://cluster.local/ns/time/sa/line"}
   221  	err = verifier(xfcc)
   222  	assert.True(envoyutil.IsValidationError(err))
   223  	expected = &envoyutil.XFCCValidationError{
   224  		Class:    envoyutil.ErrDeniedServerIdentity,
   225  		XFCC:     xfcc.String(),
   226  		Metadata: map[string]string{"serverIdentity": "line.time"},
   227  	}
   228  	assert.Equal(expected, err)
   229  }
   230  
   231  // extractJustURI satisfies `envoyutil.IdentityProvider` and just returns the URI.
   232  func extractJustURI(xfcc envoyutil.XFCCElement) (string, error) {
   233  	return xfcc.URI, nil
   234  }
   235  
   236  // extractFailure satisfies `envoyutil.IdentityProvider` and fails.
   237  func extractFailure(xfcc envoyutil.XFCCElement) (string, error) {
   238  	return "", &envoyutil.XFCCExtractionError{Class: "extractFailure", XFCC: xfcc.String()}
   239  }
   240  
   241  func makeVerifyXFCC(expectedBy string) envoyutil.VerifyXFCC {
   242  	return func(xfcc envoyutil.XFCCElement) error {
   243  		if xfcc.By == expectedBy {
   244  			return nil
   245  		}
   246  
   247  		c := ex.Class(fmt.Sprintf("verifyFailure: expected %q", expectedBy))
   248  		return &envoyutil.XFCCValidationError{Class: c, XFCC: xfcc.String()}
   249  	}
   250  }