git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #916 from oauth2-proxy/alpha-config-types

Add AlphaOptions struct to prepare for alpha config loading

commiteb07005a5ca210aaf160b9c32d9f849eed5453fa authorJoel Speed <Joel.speed@hotmail.co.uk> dateThu 19 Nov 2020, 17:11 (6 years ago) parented92df3aed43a5
12 files changed+209−37
CHANGELOG.md+1 pkg/apis/options/alpha_options.go+31 pkg/apis/options/common.go+51−3 pkg/apis/options/common_test.go+88 pkg/apis/options/header.go+6−6 pkg/apis/options/legacy_options.go+4−3 pkg/apis/options/legacy_options_test.go+4−4 pkg/apis/options/upstreams.go+13−8 pkg/upstream/http.go+2−3 pkg/upstream/http_test.go+7−7 pkg/validation/upstreams.go+1−2 pkg/validation/upstreams_test.go+1−1
CHANGELOG.md+1
@@ -43,6 +43,7 @@
4343
4444## Changes since v6.1.1
4545
46+- [#916](https://github.com/oauth2-proxy/oauth2-rpoxy/pull/916) Add AlphaOptions struct to prepare for alpha config loading (@JoelSpeed)
4647- [#923](https://github.com/oauth2-proxy/oauth2-proxy/pull/923) Support TLS 1.3 (@aajisaka)
4748- [#918](https://github.com/oauth2-proxy/oauth2-proxy/pull/918) Fix log header output (@JoelSpeed)
4849- [#911](https://github.com/oauth2-proxy/oauth2-proxy/pull/911) Validate provider type on startup.
pkg/apis/options/alpha_options.gonew file+31
@@ -0,0 +1,31 @@
1+package options
2+
3+// AlphaOptions contains alpha structured configuration options.
4+// Usage of these options allows users to access alpha features that are not
5+// available as part of the primary configuration structure for OAuth2 Proxy.
6+//
7+// :::warning
8+// The options within this structure are considered alpha.
9+// They may change between releases without notice.
10+// :::
11+type AlphaOptions struct {
12+ // Upstreams is used to configure upstream servers.
13+ // Once a user is authenticated, requests to the server will be proxied to
14+ // these upstream servers based on the path mappings defined in this list.
15+ Upstreams Upstreams `json:"upstreams,omitempty"`
16+
17+ // InjectRequestHeaders is used to configure headers that should be added
18+ // to requests to upstream servers.
19+ // Headers may source values from either the authenticated user's session
20+ // or from a static secret value.
21+ InjectRequestHeaders []Header `json:"injectRequestHeaders,omitempty"`
22+
23+ // InjectResponseHeaders is used to configure headers that should be added
24+ // to responses from the proxy.
25+ // This is typically used when using the proxy as an external authentication
26+ // provider in conjunction with another proxy such as NGINX and its
27+ // auth_request module.
28+ // Headers may source values from either the authenticated user's session
29+ // or from a static secret value.
30+ InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"`
31+}
pkg/apis/options/common.go+51−3
@@ -1,14 +1,62 @@
11package options
22
3+import (
4+ "fmt"
5+ "strconv"
6+ "time"
7+)
8+
39// SecretSource references an individual secret value.
410// Only one source within the struct should be defined at any time.
511type SecretSource struct {
612 // Value expects a base64 encoded string value.
7− Value []byte
13+ Value []byte `json:"value,omitempty"`
814
915 // FromEnv expects the name of an environment variable.
10− FromEnv string
16+ FromEnv string `json:"fromEnv,omitempty"`
1117
1218 // FromFile expects a path to a file containing the secret value.
13− FromFile string
19+ FromFile string `json:"fromFile,omitempty"`
20+}
21+
22+// Duration is an alias for time.Duration so that we can ensure the marshalling
23+// and unmarshalling of string durations is done as users expect.
24+// Intentional blank line below to keep this first part of the comment out of
25+// any generated references.
26+
27+// Duration is as string representation of a period of time.
28+// A duration string is a is a possibly signed sequence of decimal numbers,
29+// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
30+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
31+type Duration time.Duration
32+
33+// UnmarshalJSON parses the duration string and sets the value of duration
34+// to the value of the duration string.
35+func (d *Duration) UnmarshalJSON(data []byte) error {
36+ input := string(data)
37+ if unquoted, err := strconv.Unquote(input); err == nil {
38+ input = unquoted
39+ }
40+
41+ du, err := time.ParseDuration(input)
42+ if err != nil {
43+ return err
44+ }
45+ *d = Duration(du)
46+ return nil
47+}
48+
49+// MarshalJSON ensures that when the string is marshalled to JSON as a human
50+// readable string.
51+func (d *Duration) MarshalJSON() ([]byte, error) {
52+ dStr := fmt.Sprintf("%q", d.Duration().String())
53+ return []byte(dStr), nil
54+}
55+
56+// Duration returns the time.Duration version of this Duration
57+func (d *Duration) Duration() time.Duration {
58+ if d == nil {
59+ return time.Duration(0)
60+ }
61+ return time.Duration(*d)
1462}
pkg/apis/options/common_test.gonew file+88
@@ -0,0 +1,88 @@
1+package options
2+
3+import (
4+ "encoding/json"
5+ "errors"
6+ "time"
7+
8+ . "github.com/onsi/ginkgo"
9+ . "github.com/onsi/ginkgo/extensions/table"
10+ . "github.com/onsi/gomega"
11+)
12+
13+var _ = Describe("Common", func() {
14+ Context("Duration", func() {
15+ type marshalJSONTableInput struct {
16+ duration Duration
17+ expectedJSON string
18+ }
19+
20+ DescribeTable("MarshalJSON",
21+ func(in marshalJSONTableInput) {
22+ data, err := in.duration.MarshalJSON()
23+ Expect(err).ToNot(HaveOccurred())
24+ Expect(string(data)).To(Equal(in.expectedJSON))
25+
26+ var d Duration
27+ Expect(json.Unmarshal(data, &d)).To(Succeed())
28+ Expect(d).To(Equal(in.duration))
29+ },
30+ Entry("30 seconds", marshalJSONTableInput{
31+ duration: Duration(30 * time.Second),
32+ expectedJSON: "\"30s\"",
33+ }),
34+ Entry("1 minute", marshalJSONTableInput{
35+ duration: Duration(1 * time.Minute),
36+ expectedJSON: "\"1m0s\"",
37+ }),
38+ Entry("1 hour 15 minutes", marshalJSONTableInput{
39+ duration: Duration(75 * time.Minute),
40+ expectedJSON: "\"1h15m0s\"",
41+ }),
42+ Entry("A zero Duration", marshalJSONTableInput{
43+ duration: Duration(0),
44+ expectedJSON: "\"0s\"",
45+ }),
46+ )
47+
48+ type unmarshalJSONTableInput struct {
49+ json string
50+ expectedErr error
51+ expectedDuration Duration
52+ }
53+
54+ DescribeTable("UnmarshalJSON",
55+ func(in unmarshalJSONTableInput) {
56+ // A duration must be initialised pointer before UnmarshalJSON will work.
57+ zero := Duration(0)
58+ d := &zero
59+
60+ err := d.UnmarshalJSON([]byte(in.json))
61+ if in.expectedErr != nil {
62+ Expect(err).To(MatchError(in.expectedErr.Error()))
63+ } else {
64+ Expect(err).ToNot(HaveOccurred())
65+ }
66+ Expect(d).ToNot(BeNil())
67+ Expect(*d).To(Equal(in.expectedDuration))
68+ },
69+ Entry("1m", unmarshalJSONTableInput{
70+ json: "\"1m\"",
71+ expectedDuration: Duration(1 * time.Minute),
72+ }),
73+ Entry("30s", unmarshalJSONTableInput{
74+ json: "\"30s\"",
75+ expectedDuration: Duration(30 * time.Second),
76+ }),
77+ Entry("1h15m", unmarshalJSONTableInput{
78+ json: "\"1h15m\"",
79+ expectedDuration: Duration(75 * time.Minute),
80+ }),
81+ Entry("am", unmarshalJSONTableInput{
82+ json: "\"am\"",
83+ expectedErr: errors.New("time: invalid duration \"am\""),
84+ expectedDuration: Duration(0),
85+ }),
86+ )
87+ })
88+})
pkg/apis/options/header.go+6−6
@@ -5,26 +5,26 @@ package options
55type Header struct {
66 // Name is the header name to be used for this set of values.
77 // Names should be unique within a list of Headers.
8− Name string `json:"name"`
8+ Name string `json:"name,omitempty"`
99
1010 // PreserveRequestValue determines whether any values for this header
1111 // should be preserved for the request to the upstream server.
1212 // This option only takes effet on injected request headers.
1313 // Defaults to false (headers that match this header will be stripped).
14− PreserveRequestValue bool `json:"preserveRequestValue"`
14+ PreserveRequestValue bool `json:"preserveRequestValue,omitempty"`
1515
1616 // Values contains the desired values for this header
17− Values []HeaderValue `json:"values"`
17+ Values []HeaderValue `json:"values,omitempty"`
1818}
1919
2020// HeaderValue represents a single header value and the sources that can
2121// make up the header value
2222type HeaderValue struct {
2323 // Allow users to load the value from a secret source
24− *SecretSource
24+ *SecretSource `json:",omitempty"`
2525
2626 // Allow users to load the value from a session claim
27− *ClaimSource
27+ *ClaimSource `json:",omitempty"`
2828}
2929
3030// ClaimSource allows loading a header value from a claim within the session
@@ -40,5 +40,5 @@ type ClaimSource struct {
4040 // BasicAuthPassword converts this claim into a basic auth header.
4141 // Note the value of claim will become the basic auth username and the
4242 // basicAuthPassword will be used as the password value.
43− BasicAuthPassword *SecretSource
43+ BasicAuthPassword *SecretSource `json:"basicAuthPassword,omitempty"`
4444}
pkg/apis/options/legacy_options.go+4−3
@@ -27,7 +27,7 @@ func NewLegacyOptions() *LegacyOptions {
2727 LegacyUpstreams: LegacyUpstreams{
2828 PassHostHeader: true,
2929 ProxyWebSockets: true,
30− FlushInterval: time.Duration(1) * time.Second,
30+ FlushInterval: DefaultUpstreamFlushInterval,
3131 },
3232
3333 LegacyHeaders: LegacyHeaders{
@@ -62,7 +62,7 @@ type LegacyUpstreams struct {
6262func legacyUpstreamsFlagSet() *pflag.FlagSet {
6363 flagSet := pflag.NewFlagSet("upstreams", pflag.ExitOnError)
6464
65− flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
65+ flagSet.Duration("flush-interval", DefaultUpstreamFlushInterval, "period between response flushing when streaming responses")
6666 flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
6767 flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
6868 flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
@@ -84,6 +84,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
8484 u.Path = "/"
8585 }
8686
87+ flushInterval := Duration(l.FlushInterval)
8788 upstream := Upstream{
8889 ID: u.Path,
8990 Path: u.Path,
@@ -91,7 +92,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
9192 InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify,
9293 PassHostHeader: &l.PassHostHeader,
9394 ProxyWebSockets: &l.ProxyWebSockets,
94− FlushInterval: &l.FlushInterval,
95+ FlushInterval: &flushInterval,
9596 }
9697
9798 switch u.Scheme {
pkg/apis/options/legacy_options_test.go+4−4
@@ -17,8 +17,8 @@ var _ = Describe("Legacy Options", func() {
1717 legacyOpts := NewLegacyOptions()
1818
1919 // Set upstreams and related options to test their conversion
20− flushInterval := 5 * time.Second
21− legacyOpts.LegacyUpstreams.FlushInterval = flushInterval
20+ flushInterval := Duration(5 * time.Second)
21+ legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
2222 legacyOpts.LegacyUpstreams.PassHostHeader = true
2323 legacyOpts.LegacyUpstreams.ProxyWebSockets = true
2424 legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
@@ -124,7 +124,7 @@ var _ = Describe("Legacy Options", func() {
124124 skipVerify := true
125125 passHostHeader := false
126126 proxyWebSockets := true
127− flushInterval := 5 * time.Second
127+ flushInterval := Duration(5 * time.Second)
128128
129129 // Test cases and expected outcomes
130130 validHTTP := "http://foo.bar/baz"
@@ -199,7 +199,7 @@ var _ = Describe("Legacy Options", func() {
199199 SSLUpstreamInsecureSkipVerify: skipVerify,
200200 PassHostHeader: passHostHeader,
201201 ProxyWebSockets: proxyWebSockets,
202− FlushInterval: flushInterval,
202+ FlushInterval: time.Duration(flushInterval),
203203 }
204204
205205 upstreams, err := legacyUpstreams.convert()
pkg/apis/options/upstreams.go+13−8
@@ -2,6 +2,11 @@ package options
22
33import "time"
44
5+const (
6+ // DefaultUpstreamFlushInterval is the default value for the Upstream FlushInterval.
7+ DefaultUpstreamFlushInterval = 1 * time.Second
8+)
9+
510// Upstreams is a collection of definitions for upstream servers.
611type Upstreams []Upstream
712
@@ -10,11 +15,11 @@ type Upstreams []Upstream
1015type Upstream struct {
1116 // ID should be a unique identifier for the upstream.
1217 // This value is required for all upstreams.
13− ID string `json:"id"`
18+ ID string `json:"id,omitempty"`
1419
1520 // Path is used to map requests to the upstream server.
1621 // The closest match will take precedence and all Paths must be unique.
17− Path string `json:"path"`
22+ Path string `json:"path,omitempty"`
1823
1924 // The URI of the upstream server. This may be an HTTP(S) server of a File
2025 // based URL. It may include a path, in which case all requests will be served
@@ -26,19 +31,19 @@ type Upstream struct {
2631 // - file://host/path
2732 // If the URI's path is "/base" and the incoming request was for "/dir",
2833 // the upstream request will be for "/base/dir".
29− URI string `json:"uri"`
34+ URI string `json:"uri,omitempty"`
3035
3136 // InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
3237 // This option is insecure and will allow potential Man-In-The-Middle attacks
3338 // betweem OAuth2 Proxy and the usptream server.
3439 // Defaults to false.
35− InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify"`
40+ InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
3641
3742 // Static will make all requests to this upstream have a static response.
3843 // The response will have a body of "Authenticated" and a response code
3944 // matching StaticCode.
4045 // If StaticCode is not set, the response will return a 200 response.
41− Static bool `json:"static"`
46+ Static bool `json:"static,omitempty"`
4247
4348 // StaticCode determines the response code for the Static response.
4449 // This option can only be used with Static enabled.
@@ -47,14 +52,14 @@ type Upstream struct {
4752 // FlushInterval is the period between flushing the response buffer when
4853 // streaming response from the upstream.
4954 // Defaults to 1 second.
50− FlushInterval *time.Duration `json:"flushInterval,omitempty"`
55+ FlushInterval *Duration `json:"flushInterval,omitempty"`
5156
5257 // PassHostHeader determines whether the request host header should be proxied
5358 // to the upstream server.
5459 // Defaults to true.
55− PassHostHeader *bool `json:"passHostHeader"`
60+ PassHostHeader *bool `json:"passHostHeader,omitempty"`
5661
5762 // ProxyWebSockets enables proxying of websockets to upstream servers
5863 // Defaults to true.
59− ProxyWebSockets *bool `json:"proxyWebSockets"`
64+ ProxyWebSockets *bool `json:"proxyWebSockets,omitempty"`
6065}
pkg/upstream/http.go+2−3
@@ -6,7 +6,6 @@ import (
66 "net/http/httputil"
77 "net/url"
88 "strings"
9− "time"
109
1110 "github.com/mbland/hmacauth"
1211 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
@@ -98,9 +97,9 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
9897
9998 // Configure options on the SingleHostReverseProxy
10099 if upstream.FlushInterval != nil {
101− proxy.FlushInterval = *upstream.FlushInterval
100+ proxy.FlushInterval = upstream.FlushInterval.Duration()
102101 } else {
103− proxy.FlushInterval = 1 * time.Second
102+ proxy.FlushInterval = options.DefaultUpstreamFlushInterval
104103 }
105104
106105 // InsecureSkipVerify is a configurable option we allow
pkg/upstream/http_test.go+7−7
@@ -22,8 +22,8 @@ import (
2222
2323var _ = Describe("HTTP Upstream Suite", func() {
2424
25− const flushInterval5s = 5 * time.Second
26− const flushInterval1s = 1 * time.Second
25+ const flushInterval5s = options.Duration(5 * time.Second)
26+ const flushInterval1s = options.Duration(1 * time.Second)
2727 truth := true
2828 falsum := false
2929
@@ -52,7 +52,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
5252
5353 rw := httptest.NewRecorder()
5454
55− flush := 1 * time.Second
55+ flush := options.Duration(1 * time.Second)
5656
5757 upstream := options.Upstream{
5858 ID: in.id,
@@ -258,7 +258,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
258258 req := httptest.NewRequest("", "http://example.localhost/foo", nil)
259259 rw := httptest.NewRecorder()
260260
261− flush := 1 * time.Second
261+ flush := options.Duration(1 * time.Second)
262262 upstream := options.Upstream{
263263 ID: "noPassHost",
264264 PassHostHeader: &falsum,
@@ -290,7 +290,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
290290
291291 type newUpstreamTableInput struct {
292292 proxyWebSockets bool
293− flushInterval time.Duration
293+ flushInterval options.Duration
294294 skipVerify bool
295295 sigData *options.SignatureData
296296 errorHandler func(http.ResponseWriter, *http.Request, error)
@@ -319,7 +319,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
319319
320320 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
321321 Expect(ok).To(BeTrue())
322− Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
322+ Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
323323 Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
324324 if in.skipVerify {
325325 Expect(proxy.Transport).To(Equal(&http.Transport{
@@ -370,7 +370,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
370370 var proxyServer *httptest.Server
371371
372372 BeforeEach(func() {
373− flush := 1 * time.Second
373+ flush := options.Duration(1 * time.Second)
374374 upstream := options.Upstream{
375375 ID: "websocketProxy",
376376 PassHostHeader: &truth,
pkg/validation/upstreams.go+1−2
@@ -3,7 +3,6 @@ package validation
33import (
44 "fmt"
55 "net/url"
6− "time"
76
87 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
98)
@@ -70,7 +69,7 @@ func validateStaticUpstream(upstream options.Upstream) []string {
7069 if upstream.InsecureSkipTLSVerify {
7170 msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
7271 }
73− if upstream.FlushInterval != nil && *upstream.FlushInterval != time.Second {
72+ if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
7473 msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
7574 }
7675 if upstream.PassHostHeader != nil {
pkg/validation/upstreams_test.go+1−1
@@ -15,7 +15,7 @@ var _ = Describe("Upstreams", func() {
1515 errStrings []string
1616 }
1717
18− flushInterval := 5 * time.Second
18+ flushInterval := options.Duration(5 * time.Second)
1919 staticCode200 := 200
2020 truth := true
2121