github.com/blend/go-sdk@v1.20220411.3/envoyutil/middleware.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 "context" 12 "net/http" 13 14 "github.com/blend/go-sdk/web" 15 ) 16 17 // clientIdentityKey is an unexported sentinel type used to set and get the 18 // client identity string on a `context`. 19 type clientIdentityKey struct{} 20 21 // WithClientIdentity adds the client identity to a context. 22 // 23 // For example if `rc` is a `*web.Ctx` 24 // ``` 25 // rc.WithContext(envoyutil.WithClientIdentity(rc.Context(), clientIdentity)) 26 // ``` 27 func WithClientIdentity(ctx context.Context, clientIdentity string) context.Context { 28 return context.WithValue(ctx, clientIdentityKey{}, clientIdentity) 29 } 30 31 // GetClientIdentity returns the client identity of the calling service or 32 // `""` if the client identity is unset. 33 // 34 // For example if `rc` is a `*web.Ctx` 35 // ``` 36 // envoyutil.GetClientIdentity(rc.Context()) 37 // ``` 38 func GetClientIdentity(ctx context.Context) string { 39 value := ctx.Value(clientIdentityKey{}) 40 asStr, _ := value.(string) 41 // If the type assertion fails, we still want to return the zero value for string. 42 return asStr 43 } 44 45 // ClientIdentityRequired produces a middleware function that determines the 46 // client identity used in a connection secured with mTLS. 47 // 48 // This parses the `X-Forwarded-Client-Cert` (XFCC) from a request and uses 49 // a client identity provider (`cip`, e.g. see `SPIFFEClientIdentityProvider()`) to 50 // determine the client identity. Additionally, optional `verifiers` (e.g. see 51 // `SPIFFEServerIdentityProvider()`) can be used to verify other parts of the XFCC 52 // header such as the identity of the current server. 53 // 54 // In cases of error, the client identity will not be set on the current 55 // context. For error status codes 400 and 401, the error will be serialized as 56 // JSON or XML (via `ctx.DefaultProvider`) and returned in the HTTP response. 57 // For error status code 500, no identifying information from the error will be 58 // returned in the HTTP response. 59 // 60 // A 401 Unauthorized will be returned in the following cases: 61 // - The XFCC header is missing 62 // - The XFCC header (after parsing) contains zero elements or multiple elements 63 // (this code expects exactly one XFCC element, under the assumption that the 64 // Envoy `ForwardClientCertDetails` setting is configured to `SANITIZE_SET`) 65 // - The values from the XFCC header fail custom validation provided by `cip` or 66 // `verifiers`. For example, if the client identity is contained in a deny 67 // list, this would be considered a validation error. 68 // 69 // A 400 Bad Request will be returned in the following cases: 70 // - The XFCC header cannot be parsed 71 // - Custom parsing / extraction done by `cip` fails. For example, in cases 72 // where the `URI` field in the XFCC is expected to be a valid SPIFFE URI 73 // with a valid Kubernetes workload identifier, if the `URI` field does 74 // not follow that format (e.g. `urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66`) 75 // this would be considered an extraction error. 76 // 77 // A 500 Internal Server Error will be returned if the error is unrelated to 78 // validating the XFCC header or to parsing / extracting values from the XFCC 79 // header. 80 func ClientIdentityRequired(cip IdentityProvider, verifiers ...VerifyXFCC) web.Middleware { 81 return func(action web.Action) web.Action { 82 return func(ctx *web.Ctx) web.Result { 83 clientIdentity, err := ExtractAndVerifyClientIdentity(ctx.Request, cip, verifiers...) 84 if IsValidationError(err) { 85 return ctx.DefaultProvider.Status(http.StatusUnauthorized, err) 86 } 87 if IsExtractionError(err) { 88 // NOTE: We don't use `ctx.DefaultProvider.BadRequest()` because 89 // we want to allow serializing `err` as JSON if possible. 90 // The JSON provider just uses `err.Error()` for the response. 91 return ctx.DefaultProvider.Status(http.StatusBadRequest, err) 92 } 93 if err != nil { 94 return ctx.DefaultProvider.InternalError(nil) 95 } 96 97 ctx.WithContext(WithClientIdentity(ctx.Context(), clientIdentity)) 98 return action(ctx) 99 } 100 } 101 } 102 103 // ClientIdentityAware produces a middleware function nearly identical to 104 // `ClientIdentityRequired`. The primary difference is that this middleware will 105 // **not** return an error HTTP response for extraction or validation errors; 106 // it will still return a 500 Internal Server Error in unexpected failures. 107 // In cases of extraction or validation errors, the middleware will pass along 108 // to the next `action` and the client identity is not set on the current context. 109 func ClientIdentityAware(cip IdentityProvider, verifiers ...VerifyXFCC) web.Middleware { 110 return func(action web.Action) web.Action { 111 return func(ctx *web.Ctx) web.Result { 112 clientIdentity, err := ExtractAndVerifyClientIdentity(ctx.Request, cip, verifiers...) 113 // Early exit for a no-op in cases of validation or extraction error. 114 if IsValidationError(err) || IsExtractionError(err) { 115 return action(ctx) 116 } 117 118 if err != nil { 119 return ctx.DefaultProvider.InternalError(nil) 120 } 121 122 currentCtx := ctx.Context() 123 ctx.WithContext(WithClientIdentity(currentCtx, clientIdentity)) 124 return action(ctx) 125 } 126 } 127 }