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 }