Add header Injector
This commit is contained in:
		
							parent
							
								
									d9ed1e7d45
								
							
						
					
					
						commit
						ca5fd3c39f
					
				|  | @ -0,0 +1,26 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| ) | ||||
| 
 | ||||
| // GetSecretValue returns the value of the Secret from its source
 | ||||
| func GetSecretValue(source *options.SecretSource) ([]byte, error) { | ||||
| 	switch { | ||||
| 	case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "": | ||||
| 		value := make([]byte, base64.StdEncoding.DecodedLen(len(source.Value))) | ||||
| 		decoded, err := base64.StdEncoding.Decode(value, source.Value) | ||||
| 		return value[:decoded], err | ||||
| 	case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "": | ||||
| 		return []byte(os.Getenv(source.FromEnv)), nil | ||||
| 	case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "": | ||||
| 		return ioutil.ReadFile(source.FromFile) | ||||
| 	default: | ||||
| 		return nil, errors.New("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile") | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,16 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
| 
 | ||||
| func TestUtilSuite(t *testing.T) { | ||||
| 	logger.SetOutput(GinkgoWriter) | ||||
| 
 | ||||
| 	RegisterFailHandler(Fail) | ||||
| 	RunSpecs(t, "Options Util Suite") | ||||
| } | ||||
|  | @ -0,0 +1,88 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
| 
 | ||||
| var _ = Describe("GetSecretValue", func() { | ||||
| 	var fileDir string | ||||
| 	const secretEnvKey = "SECRET_ENV_KEY" | ||||
| 	const secretEnvValue = "secret-env-value" | ||||
| 	var secretFileValue = []byte("secret-file-value") | ||||
| 
 | ||||
| 	BeforeEach(func() { | ||||
| 		os.Setenv(secretEnvKey, secretEnvValue) | ||||
| 
 | ||||
| 		var err error | ||||
| 		fileDir, err = ioutil.TempDir("", "oauth2-proxy-util-get-secret-value") | ||||
| 		Expect(err).ToNot(HaveOccurred()) | ||||
| 		Expect(ioutil.WriteFile(path.Join(fileDir, "secret-file"), secretFileValue, 0600)).To(Succeed()) | ||||
| 	}) | ||||
| 
 | ||||
| 	AfterEach(func() { | ||||
| 		os.Unsetenv(secretEnvKey) | ||||
| 		os.RemoveAll(fileDir) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("returns the correct value from base64", func() { | ||||
| 		originalValue := []byte("secret-value-1") | ||||
| 		b64Value := base64.StdEncoding.EncodeToString((originalValue)) | ||||
| 
 | ||||
| 		// Once encoded, the originalValue could have a decoded length longer than
 | ||||
| 		// its actual length, ensure we trim this.
 | ||||
| 		// This assertion ensures we are testing the triming
 | ||||
| 		Expect(len(originalValue)).To(BeNumerically("<", base64.StdEncoding.DecodedLen(len(b64Value)))) | ||||
| 
 | ||||
| 		value, err := GetSecretValue(&options.SecretSource{ | ||||
| 			Value: []byte(b64Value), | ||||
| 		}) | ||||
| 		Expect(err).ToNot(HaveOccurred()) | ||||
| 		Expect(value).To(Equal(originalValue)) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("returns the correct value from the environment", func() { | ||||
| 		value, err := GetSecretValue(&options.SecretSource{ | ||||
| 			FromEnv: secretEnvKey, | ||||
| 		}) | ||||
| 		Expect(err).ToNot(HaveOccurred()) | ||||
| 		Expect(value).To(BeEquivalentTo(secretEnvValue)) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("returns the correct value from a file", func() { | ||||
| 		value, err := GetSecretValue(&options.SecretSource{ | ||||
| 			FromFile: path.Join(fileDir, "secret-file"), | ||||
| 		}) | ||||
| 		Expect(err).ToNot(HaveOccurred()) | ||||
| 		Expect(value).To(Equal(secretFileValue)) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("when the file does not exist", func() { | ||||
| 		value, err := GetSecretValue(&options.SecretSource{ | ||||
| 			FromFile: path.Join(fileDir, "not-exist"), | ||||
| 		}) | ||||
| 		Expect(err).To(HaveOccurred()) | ||||
| 		Expect(value).To(BeEmpty()) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("with no source set", func() { | ||||
| 		value, err := GetSecretValue(&options.SecretSource{}) | ||||
| 		Expect(err).To(MatchError("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile")) | ||||
| 		Expect(value).To(BeEmpty()) | ||||
| 	}) | ||||
| 
 | ||||
| 	It("with multiple sources set", func() { | ||||
| 		value, err := GetSecretValue(&options.SecretSource{ | ||||
| 			FromEnv:  secretEnvKey, | ||||
| 			FromFile: path.Join(fileDir, "secret-file"), | ||||
| 		}) | ||||
| 		Expect(err).To(MatchError("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile")) | ||||
| 		Expect(value).To(BeEmpty()) | ||||
| 	}) | ||||
| }) | ||||
|  | @ -8,6 +8,7 @@ import ( | |||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
| 
 | ||||
|  | @ -69,6 +70,34 @@ func (s *SessionState) String() string { | |||
| 	return o + "}" | ||||
| } | ||||
| 
 | ||||
| func (s *SessionState) GetClaim(claim string) string { | ||||
| 	if s == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	switch claim { | ||||
| 	case "access_token": | ||||
| 		return s.AccessToken | ||||
| 	case "id_token": | ||||
| 		return s.IDToken | ||||
| 	case "created_at": | ||||
| 		return s.CreatedAt.String() | ||||
| 	case "expires_on": | ||||
| 		return s.ExpiresOn.String() | ||||
| 	case "refresh_token": | ||||
| 		return s.RefreshToken | ||||
| 	case "email": | ||||
| 		return s.Email | ||||
| 	case "user": | ||||
| 		return s.User | ||||
| 	case "groups": | ||||
| 		return strings.Join(s.Groups, ",") | ||||
| 	case "preferred_username": | ||||
| 		return s.PreferredUsername | ||||
| 	default: | ||||
| 		return "" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // EncodeSessionState returns an encrypted, lz4 compressed, MessagePack encoded session
 | ||||
| func (s *SessionState) EncodeSessionState(c encryption.Cipher, compress bool) ([]byte, error) { | ||||
| 	packed, err := msgpack.Marshal(s) | ||||
|  |  | |||
|  | @ -0,0 +1,37 @@ | |||
| package header | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	filesDir string | ||||
| ) | ||||
| 
 | ||||
| func TestHeaderSuite(t *testing.T) { | ||||
| 	logger.SetOutput(GinkgoWriter) | ||||
| 
 | ||||
| 	RegisterFailHandler(Fail) | ||||
| 	RunSpecs(t, "Header") | ||||
| } | ||||
| 
 | ||||
| var _ = BeforeSuite(func() { | ||||
| 	os.Setenv("SECRET_ENV", "super-secret-env") | ||||
| 
 | ||||
| 	dir, err := ioutil.TempDir("", "oauth2-proxy-header-suite") | ||||
| 	Expect(err).ToNot(HaveOccurred()) | ||||
| 	Expect(ioutil.WriteFile(path.Join(dir, "secret-file"), []byte("super-secret-file"), 0644)).To(Succeed()) | ||||
| 	filesDir = dir | ||||
| }) | ||||
| 
 | ||||
| var _ = AfterSuite(func() { | ||||
| 	os.Unsetenv("SECRET_ENV") | ||||
| 	Expect(os.RemoveAll(filesDir)).To(Succeed()) | ||||
| }) | ||||
|  | @ -0,0 +1,112 @@ | |||
| package header | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util" | ||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||
| ) | ||||
| 
 | ||||
| type Injector interface { | ||||
| 	Inject(http.Header, *sessionsapi.SessionState) | ||||
| } | ||||
| 
 | ||||
| type injector struct { | ||||
| 	valueInjectors []valueInjector | ||||
| } | ||||
| 
 | ||||
| func (i injector) Inject(header http.Header, session *sessionsapi.SessionState) { | ||||
| 	for _, injector := range i.valueInjectors { | ||||
| 		injector.inject(header, session) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func NewInjector(headers []options.Header) (Injector, error) { | ||||
| 	injectors := []valueInjector{} | ||||
| 	for _, header := range headers { | ||||
| 		for _, value := range header.Values { | ||||
| 			injector, err := newValueinjector(header.Name, value) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("error building injector for header %q: %v", header.Name, err) | ||||
| 			} | ||||
| 			injectors = append(injectors, injector) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return &injector{valueInjectors: injectors}, nil | ||||
| } | ||||
| 
 | ||||
| type valueInjector interface { | ||||
| 	inject(http.Header, *sessionsapi.SessionState) | ||||
| } | ||||
| 
 | ||||
| func newValueinjector(name string, value options.HeaderValue) (valueInjector, error) { | ||||
| 	switch { | ||||
| 	case value.SecretSource != nil && value.ClaimSource == nil: | ||||
| 		return newSecretInjector(name, value.SecretSource) | ||||
| 	case value.SecretSource == nil && value.ClaimSource != nil: | ||||
| 		return newClaimInjector(name, value.ClaimSource) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("header %q value has multiple entries: only one entry per value is allowed", name) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type injectorFunc struct { | ||||
| 	injectFunc func(http.Header, *sessionsapi.SessionState) | ||||
| } | ||||
| 
 | ||||
| func (i *injectorFunc) inject(header http.Header, session *sessionsapi.SessionState) { | ||||
| 	i.injectFunc(header, session) | ||||
| } | ||||
| 
 | ||||
| func newInjectorFunc(injectFunc func(header http.Header, session *sessionsapi.SessionState)) valueInjector { | ||||
| 	return &injectorFunc{injectFunc: injectFunc} | ||||
| } | ||||
| 
 | ||||
| func newSecretInjector(name string, source *options.SecretSource) (valueInjector, error) { | ||||
| 	value, err := util.GetSecretValue(source) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error getting secret value: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) { | ||||
| 		header.Add(name, string(value)) | ||||
| 	}), nil | ||||
| } | ||||
| 
 | ||||
| func newClaimInjector(name string, source *options.ClaimSource) (valueInjector, error) { | ||||
| 	switch { | ||||
| 	case source.BasicAuthPassword != nil: | ||||
| 		password, err := util.GetSecretValue(source.BasicAuthPassword) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("error loading basicAuthPassword: %v", err) | ||||
| 		} | ||||
| 		return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) { | ||||
| 			claim := session.GetClaim(source.Claim) | ||||
| 			if claim == "" { | ||||
| 				return | ||||
| 			} | ||||
| 			auth := claim + ":" + string(password) | ||||
| 			header.Add(name, "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) | ||||
| 		}), nil | ||||
| 	case source.Prefix != "": | ||||
| 		return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) { | ||||
| 			claim := session.GetClaim(source.Claim) | ||||
| 			if claim == "" { | ||||
| 				return | ||||
| 			} | ||||
| 			header.Add(name, source.Prefix+claim) | ||||
| 		}), nil | ||||
| 	default: | ||||
| 		return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) { | ||||
| 			claim := session.GetClaim(source.Claim) | ||||
| 			if claim == "" { | ||||
| 				return | ||||
| 			} | ||||
| 			header.Add(name, claim) | ||||
| 		}), nil | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,417 @@ | |||
| package header | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/ginkgo/extensions/table" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
| 
 | ||||
| var _ = Describe("Injector Suite", func() { | ||||
| 	Context("NewInjector", func() { | ||||
| 		type newInjectorTableInput struct { | ||||
| 			headers         []options.Header | ||||
| 			initialHeaders  http.Header | ||||
| 			session         *sessionsapi.SessionState | ||||
| 			expectedHeaders http.Header | ||||
| 			expectedErr     error | ||||
| 		} | ||||
| 
 | ||||
| 		DescribeTable("creates an injector", | ||||
| 			func(in newInjectorTableInput) { | ||||
| 				injector, err := NewInjector(in.headers) | ||||
| 				if in.expectedErr != nil { | ||||
| 					Expect(err).To(MatchError(in.expectedErr)) | ||||
| 					Expect(injector).To(BeNil()) | ||||
| 					return | ||||
| 				} | ||||
| 
 | ||||
| 				Expect(err).ToNot(HaveOccurred()) | ||||
| 				Expect(injector).ToNot(BeNil()) | ||||
| 
 | ||||
| 				headers := in.initialHeaders.Clone() | ||||
| 				injector.Inject(headers, in.session) | ||||
| 				Expect(headers).To(Equal(in.expectedHeaders)) | ||||
| 			}, | ||||
| 			Entry("with no configured headers", newInjectorTableInput{ | ||||
| 				headers: []options.Header{}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a static valued header from base64", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Secret", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									Value: []byte(base64.StdEncoding.EncodeToString([]byte("super-secret"))), | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":    []string{"bar", "baz"}, | ||||
| 					"Secret": []string{"super-secret"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a static valued header from env", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Secret", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									FromEnv: "SECRET_ENV", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":    []string{"bar", "baz"}, | ||||
| 					"Secret": []string{"super-secret-env"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a claim valued header", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Claim", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "id_token", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					IDToken: "IDToken-1234", | ||||
| 				}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":   []string{"bar", "baz"}, | ||||
| 					"Claim": []string{"IDToken-1234"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a claim valued header and a nil session", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Claim", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "id_token", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: nil, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a prefixed claim valued header", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Claim", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim:  "id_token", | ||||
| 									Prefix: "Bearer ", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					IDToken: "IDToken-1234", | ||||
| 				}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":   []string{"bar", "baz"}, | ||||
| 					"Claim": []string{"Bearer IDToken-1234"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a prefixed claim valued header missing the claim", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Claim", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim:  "idToken", | ||||
| 									Prefix: "Bearer ", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a basicAuthPassword and claim valued header", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Authorization", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 									BasicAuthPassword: &options.SecretSource{ | ||||
| 										Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					User: "user-123", | ||||
| 				}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":                          []string{"bar", "baz"}, | ||||
| 					"X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a basicAuthPassword and claim valued header missing the claim", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Authorization", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 									BasicAuthPassword: &options.SecretSource{ | ||||
| 										Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a header that already exists", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-User", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"X-Auth-Request-User": []string{"user"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					User: "user-123", | ||||
| 				}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"X-Auth-Request-User": []string{"user", "user-123"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 			Entry("with a claim and secret valued header value", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Claim", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "id_token", | ||||
| 								}, | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									FromEnv: "SECRET_ENV", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					IDToken: "IDToken-1234", | ||||
| 				}, | ||||
| 				expectedHeaders: nil, | ||||
| 				expectedErr:     errors.New("error building injector for header \"Claim\": header \"Claim\" value has multiple entries: only one entry per value is allowed"), | ||||
| 			}), | ||||
| 			Entry("with an invalid static valued header", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "Secret", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									FromEnv:  "SECRET_ENV", | ||||
| 									FromFile: "secret-file", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session:         &sessionsapi.SessionState{}, | ||||
| 				expectedHeaders: nil, | ||||
| 				expectedErr:     errors.New("error building injector for header \"Secret\": error getting secret value: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"), | ||||
| 			}), | ||||
| 			Entry("with an invalid basicAuthPassword claim valued header", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Authorization", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 									BasicAuthPassword: &options.SecretSource{ | ||||
| 										Value:   []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), | ||||
| 										FromEnv: "SECRET_ENV", | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					User: "user-123", | ||||
| 				}, | ||||
| 				expectedHeaders: nil, | ||||
| 				expectedErr:     errors.New("error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"), | ||||
| 			}), | ||||
| 			Entry("with a mix of configured headers", newInjectorTableInput{ | ||||
| 				headers: []options.Header{ | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Authorization", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 									BasicAuthPassword: &options.SecretSource{ | ||||
| 										Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), | ||||
| 									}, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-User", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "user", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Email", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								ClaimSource: &options.ClaimSource{ | ||||
| 									Claim: "email", | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "X-Auth-Request-Version-Info", | ||||
| 						Values: []options.HeaderValue{ | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									Value: []byte(base64.StdEncoding.EncodeToString([]byte("major=1"))), | ||||
| 								}, | ||||
| 							}, | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									Value: []byte(base64.StdEncoding.EncodeToString([]byte("minor=2"))), | ||||
| 								}, | ||||
| 							}, | ||||
| 							{ | ||||
| 								SecretSource: &options.SecretSource{ | ||||
| 									Value: []byte(base64.StdEncoding.EncodeToString([]byte("patch=3"))), | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				initialHeaders: http.Header{ | ||||
| 					"foo": []string{"bar", "baz"}, | ||||
| 				}, | ||||
| 				session: &sessionsapi.SessionState{ | ||||
| 					User:  "user-123", | ||||
| 					Email: "user@example.com", | ||||
| 				}, | ||||
| 				expectedHeaders: http.Header{ | ||||
| 					"foo":                          []string{"bar", "baz"}, | ||||
| 					"X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))}, | ||||
| 					"X-Auth-Request-User":          []string{"user-123"}, | ||||
| 					"X-Auth-Request-Email":         []string{"user@example.com"}, | ||||
| 					"X-Auth-Request-Version-Info":  []string{"major=1", "minor=2", "patch=3"}, | ||||
| 				}, | ||||
| 				expectedErr: nil, | ||||
| 			}), | ||||
| 		) | ||||
| 	}) | ||||
| }) | ||||
		Loading…
	
		Reference in New Issue