git.iko.soy/typeable/oauth2-proxy
on master help sign in
fix: hmacauth dependency licensing issue (#3253)
fcf4e79Jan Larwig
go.mod+1−3
@@ -20,7 +20,6 @@ require (
20 github.com/google/uuid v1.6.0
20 github.com/google/uuid v1.6.0
21 github.com/gorilla/mux v1.8.1
21 github.com/gorilla/mux v1.8.1
22 github.com/justinas/alice v1.2.0
22 github.com/justinas/alice v1.2.0
23− github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
24 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
23 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
25 github.com/onsi/ginkgo/v2 v2.23.4
24 github.com/onsi/ginkgo/v2 v2.23.4
26 github.com/onsi/gomega v1.37.0
25 github.com/onsi/gomega v1.37.0
@@ -30,7 +29,7 @@ require (
30 github.com/spf13/cast v1.9.2
29 github.com/spf13/cast v1.9.2
31 github.com/spf13/pflag v1.0.7
30 github.com/spf13/pflag v1.0.7
32 github.com/spf13/viper v1.20.1
31 github.com/spf13/viper v1.20.1
33− github.com/stretchr/testify v1.10.0
32+ github.com/stretchr/testify v1.11.1
34 github.com/vmihailenco/msgpack/v5 v5.4.1
33 github.com/vmihailenco/msgpack/v5 v5.4.1
35 golang.org/x/crypto v0.40.0
34 golang.org/x/crypto v0.40.0
36 golang.org/x/net v0.42.0
35 golang.org/x/net v0.42.0
@@ -45,7 +44,6 @@ require (
45 cloud.google.com/go/auth v0.16.2 // indirect
44 cloud.google.com/go/auth v0.16.2 // indirect
46 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
45 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
47 github.com/beorn7/perks v1.0.1 // indirect
46 github.com/beorn7/perks v1.0.1 // indirect
48− github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
49 github.com/cespare/xxhash/v2 v2.3.0 // indirect
47 github.com/cespare/xxhash/v2 v2.3.0 // indirect
50 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
48 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
51 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
49 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
go.sum+2−2
@@ -18,8 +18,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
18github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
18github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
19github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
19github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
20github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
20github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
21−github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
22−github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
23github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
21github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
24github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
22github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
25github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
23github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -142,6 +140,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
142github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
140github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
143github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
141github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
144github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
142github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
143+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
144+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
145github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
145github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
146github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
146github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
147github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
147github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
oauthproxy_test.go+1−1
@@ -15,9 +15,9 @@ import (
15 "time"
15 "time"
16
16
17 "github.com/coreos/go-oidc/v3/oidc"
17 "github.com/coreos/go-oidc/v3/oidc"
18− "github.com/mbland/hmacauth"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
20 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
19 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
20+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
21 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
22 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
23 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
23 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
pkg/authentication/hmacauth/LICENSE.mdnew file+5
@@ -0,0 +1,5 @@
1+Copyright (c) 2017 Mike Bland mbland@acm.org
2+
3+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4+
5+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
pkg/authentication/hmacauth/hmacauth.gonew file+237
@@ -0,0 +1,237 @@
1+package hmacauth
2+
3+import (
4+ "bytes"
5+ "crypto"
6+ "crypto/hmac"
7+ "encoding/base64"
8+ "errors"
9+ "fmt"
10+ "io"
11+ "net/http"
12+ "strings"
13+)
14+
15+// HmacAuth signs outbound requests and authenticates inbound requests.
16+type HmacAuth interface {
17+ // Produces the string that will be prefixed to the request body and
18+ // used to generate the signature.
19+ StringToSign(req *http.Request) string
20+
21+ // Adds a signature header to the request.
22+ SignRequest(req *http.Request)
23+
24+ // Generates a signature for the request.
25+ RequestSignature(req *http.Request) string
26+
27+ // Retrieves the signature included in the request header.
28+ SignatureFromHeader(req *http.Request) string
29+
30+ // Authenticates the request, returning the result code, the signature
31+ // from the header, and the locally-computed signature.
32+ AuthenticateRequest(request *http.Request) (
33+ result AuthenticationResult,
34+ headerSignature, computedSignature string)
35+}
36+
37+var supportedAlgorithms = map[string]crypto.Hash{
38+ "md4": crypto.MD4,
39+ "md5": crypto.MD5,
40+ "sha1": crypto.SHA1,
41+ "sha224": crypto.SHA224,
42+ "sha256": crypto.SHA256,
43+ "sha384": crypto.SHA384,
44+ "sha512": crypto.SHA512,
45+ "ripemd160": crypto.RIPEMD160,
46+}
47+
48+var algorithmName map[crypto.Hash]string
49+
50+func init() {
51+ algorithmName = make(map[crypto.Hash]string)
52+ for name, algorithm := range supportedAlgorithms {
53+ algorithmName[algorithm] = name
54+ // Make sure the algorithm is linked into the binary, per
55+ // https://golang.org/pkg/crypto/#Hash.Available
56+ //
57+ // Note that both sides of the client/server connection must
58+ // have an algorithm available in order to successfully
59+ // authenticate using that algorithm
60+ if !algorithm.Available() {
61+ delete(supportedAlgorithms, name)
62+ }
63+ }
64+}
65+
66+// DigestNameToCryptoHash returns the crypto.Hash value corresponding to the
67+// algorithm name, or an error if the algorithm is not supported.
68+func DigestNameToCryptoHash(name string) (result crypto.Hash, err error) {
69+ var supported bool
70+ if result, supported = supportedAlgorithms[name]; !supported {
71+ err = errors.New("hmacauth: hash algorithm not supported: " +
72+ name)
73+ }
74+ return
75+}
76+
77+// CryptoHashToDigestName returns the algorithm name corresponding to the
78+// crypto.Hash ID, or an error if the algorithm is not supported.
79+func CryptoHashToDigestName(id crypto.Hash) (result string, err error) {
80+ var supported bool
81+ if result, supported = algorithmName[id]; !supported {
82+ err = fmt.Errorf("hmacauth: unsupported crypto.Hash #%d", id)
83+ }
84+ return
85+}
86+
87+type hmacAuth struct {
88+ hash crypto.Hash
89+ key []byte
90+ header string
91+ headers []string
92+}
93+
94+// NewHmacAuth returns an HmacAuth object that can be used to sign or
95+// authenticate HTTP requests based on the supplied parameters.
96+func NewHmacAuth(hash crypto.Hash, key []byte, header string,
97+ headers []string) HmacAuth {
98+ if !hash.Available() {
99+ var name string
100+ var supported bool
101+ if name, supported = algorithmName[hash]; !supported {
102+ name = fmt.Sprintf("#%d", hash)
103+ }
104+ panic("hmacauth: hash algorithm " + name + " is unavailable")
105+ }
106+ canonicalHeaders := make([]string, len(headers))
107+ for i, h := range headers {
108+ canonicalHeaders[i] = http.CanonicalHeaderKey(h)
109+ }
110+ return &hmacAuth{hash, key, header, canonicalHeaders}
111+}
112+
113+func (auth *hmacAuth) StringToSign(req *http.Request) string {
114+ var buffer bytes.Buffer
115+ _, _ = buffer.WriteString(req.Method)
116+ _, _ = buffer.WriteString("\n")
117+
118+ for _, header := range auth.headers {
119+ values := req.Header[header]
120+ lastIndex := len(values) - 1
121+ for i, value := range values {
122+ _, _ = buffer.WriteString(value)
123+ if i != lastIndex {
124+ _, _ = buffer.WriteString(",")
125+ }
126+ }
127+ _, _ = buffer.WriteString("\n")
128+ }
129+ _, _ = buffer.WriteString(req.URL.Path)
130+ if req.URL.RawQuery != "" {
131+ _, _ = buffer.WriteString("?")
132+ _, _ = buffer.WriteString(req.URL.RawQuery)
133+ }
134+ if req.URL.Fragment != "" {
135+ _, _ = buffer.WriteString("#")
136+ _, _ = buffer.WriteString(req.URL.Fragment)
137+ }
138+ _, _ = buffer.WriteString("\n")
139+ return buffer.String()
140+}
141+
142+func (auth *hmacAuth) SignRequest(req *http.Request) {
143+ req.Header.Set(auth.header, auth.RequestSignature(req))
144+}
145+
146+func (auth *hmacAuth) RequestSignature(req *http.Request) string {
147+ return requestSignature(auth, req, auth.hash)
148+}
149+
150+func requestSignature(auth *hmacAuth, req *http.Request,
151+ hashAlgorithm crypto.Hash) string {
152+ h := hmac.New(hashAlgorithm.New, auth.key)
153+ _, _ = h.Write([]byte(auth.StringToSign(req)))
154+
155+ if req.Body != nil {
156+ reqBody, _ := io.ReadAll(req.Body)
157+ req.Body = io.NopCloser(bytes.NewBuffer(reqBody))
158+ _, _ = h.Write(reqBody)
159+ }
160+
161+ var sig []byte
162+ sig = h.Sum(sig)
163+ return algorithmName[hashAlgorithm] + " " +
164+ base64.StdEncoding.EncodeToString(sig)
165+}
166+
167+func (auth *hmacAuth) SignatureFromHeader(req *http.Request) string {
168+ return req.Header.Get(auth.header)
169+}
170+
171+// AuthenticationResult is a code used to identify the outcome of
172+// HmacAuth.AuthenticateRequest().
173+type AuthenticationResult int
174+
175+const (
176+ // ResultNoSignature - the incoming result did not have a signature
177+ // header.
178+ ResultNoSignature AuthenticationResult = iota
179+
180+ // ResultInvalidFormat - the signature header was not parseable.
181+ ResultInvalidFormat
182+
183+ // ResultUnsupportedAlgorithm - the signature header specified an
184+ // unsupported algorithm.
185+ ResultUnsupportedAlgorithm
186+
187+ // ResultMatch - the signature from the request header matched the
188+ // locally-computed signature.
189+ ResultMatch
190+
191+ // ResultMismatch - the signature from the request header did not match
192+ // the locally-computed signature.
193+ ResultMismatch
194+)
195+
196+var validationResultStrings = []string{
197+ "",
198+ "ResultNoSignature",
199+ "ResultInvalidFormat",
200+ "ResultUnsupportedAlgorithm",
201+ "ResultMatch",
202+ "ResultMismatch",
203+}
204+
205+func (result AuthenticationResult) String() string {
206+ return validationResultStrings[result]
207+}
208+
209+func (auth *hmacAuth) AuthenticateRequest(request *http.Request) (
210+ result AuthenticationResult, headerSignature,
211+ computedSignature string) {
212+ headerSignature = auth.SignatureFromHeader(request)
213+ if headerSignature == "" {
214+ result = ResultNoSignature
215+ return
216+ }
217+
218+ components := strings.Split(headerSignature, " ")
219+ if len(components) != 2 {
220+ result = ResultInvalidFormat
221+ return
222+ }
223+
224+ algorithm, err := DigestNameToCryptoHash(components[0])
225+ if err != nil {
226+ result = ResultUnsupportedAlgorithm
227+ return
228+ }
229+
230+ computedSignature = requestSignature(auth, request, algorithm)
231+ if hmac.Equal([]byte(headerSignature), []byte(computedSignature)) {
232+ result = ResultMatch
233+ } else {
234+ result = ResultMismatch
235+ }
236+ return
237+}
pkg/authentication/hmacauth/hmacauth_test.gonew file+302
@@ -0,0 +1,302 @@
1+package hmacauth
2+
3+import (
4+ "bufio"
5+ "crypto"
6+ "io"
7+ "net/http"
8+ "net/http/httptest"
9+ "strconv"
10+ "strings"
11+ "testing"
12+
13+ "github.com/stretchr/testify/assert"
14+)
15+
16+// These correspond to the headers used in bitly/oauth2_proxy#147.
17+var HEADERS = []string{
18+ "Content-Length",
19+ "Content-Md5",
20+ "Content-Type",
21+ "Date",
22+ "Authorization",
23+ "X-Forwarded-User",
24+ "X-Forwarded-Email",
25+ "X-Forwarded-Access-Token",
26+ "Cookie",
27+ "Gap-Auth",
28+}
29+
30+func TestSupportedHashAlgorithm(t *testing.T) {
31+ algorithm, err := DigestNameToCryptoHash("sha1")
32+ assert.Equal(t, err, nil)
33+ assert.Equal(t, algorithm, crypto.SHA1)
34+ assert.Equal(t, algorithm.Available(), true)
35+}
36+
37+func TestUnsupportedHashAlgorithm(t *testing.T) {
38+ algorithm, err := DigestNameToCryptoHash("unsupported")
39+ assert.NotEqual(t, err, nil)
40+ assert.Equal(t, err.Error(),
41+ "hmacauth: hash algorithm not supported: unsupported")
42+ assert.Equal(t, algorithm, crypto.Hash(0))
43+ assert.Equal(t, algorithm.Available(), false)
44+}
45+
46+func TestResultUnsupportedAlgorithmWillCauseNewHmacAuthToPanic(t *testing.T) {
47+ defer func() {
48+ err := recover()
49+ assert.Equal(t, err,
50+ "hmacauth: hash algorithm #0 is unavailable")
51+ }()
52+ NewHmacAuth(crypto.Hash(0), nil, "", nil)
53+}
54+
55+func newTestRequest(request ...string) (req *http.Request) {
56+ reqBuf := bufio.NewReader(
57+ strings.NewReader(strings.Join(request, "\n")))
58+ if req, err := http.ReadRequest(reqBuf); err != nil {
59+ panic(err)
60+ } else {
61+ return req
62+ }
63+}
64+
65+func testHmacAuth() HmacAuth {
66+ return NewHmacAuth(
67+ crypto.SHA1, []byte("foobar"), "GAP-Signature", HEADERS)
68+}
69+
70+func TestRequestSignaturePost(t *testing.T) {
71+ body := `{ "hello": "world!" }`
72+ req := newTestRequest(
73+ "POST /foo/bar HTTP/1.1",
74+ "Content-Length: "+strconv.Itoa(len(body)),
75+ "Content-MD5: deadbeef",
76+ "Content-Type: application/json",
77+ "Date: 2015-09-28",
78+ "Authorization: trust me",
79+ "X-Forwarded-User: mbland",
80+ "X-Forwarded-Email: mbland@acm.org",
81+ "X-Forwarded-Access-Token: feedbead",
82+ "Cookie: foo; bar; baz=quux",
83+ "Gap-Auth: mbland",
84+ "",
85+ body,
86+ )
87+
88+ h := testHmacAuth()
89+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
90+ "POST",
91+ strconv.Itoa(len(body)),
92+ "deadbeef",
93+ "application/json",
94+ "2015-09-28",
95+ "trust me",
96+ "mbland",
97+ "mbland@acm.org",
98+ "feedbead",
99+ "foo; bar; baz=quux",
100+ "mbland",
101+ "/foo/bar",
102+ }, "\n")+"\n")
103+ assert.Equal(t, h.RequestSignature(req),
104+ "sha1 K4IrVDtMCRwwW8Oms0VyZWMjXHI=")
105+
106+ if requestBody, err := io.ReadAll(req.Body); err != nil {
107+ panic(err)
108+ } else {
109+ assert.Equal(t, string(requestBody), body)
110+ }
111+}
112+
113+func newGetRequest() *http.Request {
114+ return newTestRequest(
115+ "GET /foo/bar HTTP/1.1",
116+ "Date: 2015-09-29",
117+ "Cookie: foo; bar; baz=quux",
118+ "Gap-Auth: mbland",
119+ "",
120+ "",
121+ )
122+}
123+
124+func TestRequestSignatureGetWithFullUrl(t *testing.T) {
125+ req := newTestRequest(
126+ "GET http://localhost/foo/bar?baz=quux%2Fxyzzy#plugh HTTP/1.1",
127+ "Date: 2015-09-29",
128+ "Cookie: foo; bar; baz=quux",
129+ "Gap-Auth: mbland",
130+ "",
131+ "",
132+ )
133+
134+ h := testHmacAuth()
135+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
136+ "GET",
137+ "",
138+ "",
139+ "",
140+ "2015-09-29",
141+ "",
142+ "",
143+ "",
144+ "",
145+ "foo; bar; baz=quux",
146+ "mbland",
147+ "/foo/bar?baz=quux%2Fxyzzy#plugh",
148+ }, "\n")+"\n")
149+ assert.Equal(t, h.RequestSignature(req),
150+ "sha1 ih5Jce9nsltry63rR4ImNz2hdnk=")
151+}
152+
153+func TestRequestSignatureGetWithMultipleHeadersWithTheSameName(t *testing.T) {
154+ // Just using "Cookie:" out of convenience.
155+ req := newTestRequest(
156+ "GET /foo/bar HTTP/1.1",
157+ "Date: 2015-09-29",
158+ "Cookie: foo",
159+ "Cookie: bar",
160+ "Cookie: baz=quux",
161+ "Gap-Auth: mbland",
162+ "",
163+ "",
164+ )
165+
166+ h := testHmacAuth()
167+ assert.Equal(t, h.StringToSign(req), strings.Join([]string{
168+ "GET",
169+ "",
170+ "",
171+ "",
172+ "2015-09-29",
173+ "",
174+ "",
175+ "",
176+ "",
177+ "foo,bar,baz=quux",
178+ "mbland",
179+ "/foo/bar",
180+ }, "\n")+"\n")
181+ assert.Equal(t, h.RequestSignature(req),
182+ "sha1 JlRkes1X+qq3Bgc/GcRyLos+4aI=")
183+}
184+
185+func TestAuthenticateRequestResultNoSignature(t *testing.T) {
186+ h := testHmacAuth()
187+ req := newGetRequest()
188+ result, header, computed := h.AuthenticateRequest(req)
189+ assert.Equal(t, result, ResultNoSignature)
190+ assert.Equal(t, header, "")
191+ assert.Equal(t, computed, "")
192+}
193+
194+func TestAuthenticateRequestResultInvalidFormat(t *testing.T) {
195+ h := testHmacAuth()
196+ req := newGetRequest()
197+ badValue := "should be algorithm and digest value"
198+ req.Header.Set("GAP-Signature", badValue)
199+ result, header, computed := h.AuthenticateRequest(req)
200+ assert.Equal(t, result, ResultInvalidFormat)
201+ assert.Equal(t, header, badValue)
202+ assert.Equal(t, computed, "")
203+}
204+
205+func TestAuthenticateRequestResultUnsupportedAlgorithm(t *testing.T) {
206+ h := testHmacAuth()
207+ req := newGetRequest()
208+ validSignature := h.RequestSignature(req)
209+ components := strings.Split(validSignature, " ")
210+ signatureWithResultUnsupportedAlgorithm := "unsupported " +
211+ components[1]
212+ req.Header.Set("GAP-Signature", signatureWithResultUnsupportedAlgorithm)
213+ result, header, computed := h.AuthenticateRequest(req)
214+ assert.Equal(t, result, ResultUnsupportedAlgorithm)
215+ assert.Equal(t, header, signatureWithResultUnsupportedAlgorithm)
216+ assert.Equal(t, computed, "")
217+}
218+
219+func TestAuthenticateRequestResultMatch(t *testing.T) {
220+ h := testHmacAuth()
221+ req := newGetRequest()
222+ expected := h.RequestSignature(req)
223+ h.SignRequest(req)
224+ result, header, computed := h.AuthenticateRequest(req)
225+ assert.Equal(t, result, ResultMatch)
226+ assert.Equal(t, header, expected)
227+ assert.Equal(t, computed, expected)
228+}
229+
230+func TestAuthenticateRequestMismatch(t *testing.T) {
231+ foobarAuth := testHmacAuth()
232+ barbazAuth := NewHmacAuth(
233+ crypto.SHA1, []byte("barbaz"), "GAP-Signature", HEADERS)
234+ req := newGetRequest()
235+ foobarAuth.SignRequest(req)
236+ result, header, computed := barbazAuth.AuthenticateRequest(req)
237+ assert.Equal(t, result, ResultMismatch)
238+ assert.Equal(t, header, foobarAuth.RequestSignature(req))
239+ assert.Equal(t, computed, barbazAuth.RequestSignature(req))
240+}
241+
242+type SignatureAuthenticator struct {
243+ auth HmacAuth
244+}
245+
246+func (v *SignatureAuthenticator) Authenticate(
247+ w http.ResponseWriter, r *http.Request) {
248+ result, headerSig, computedSig := v.auth.AuthenticateRequest(r)
249+ switch result {
250+ case ResultNoSignature:
251+ w.Write([]byte("no signature received"))
252+ case ResultMatch:
253+ w.Write([]byte("signatures match"))
254+ case ResultMismatch:
255+ w.Write([]byte("signatures do not match:" +
256+ "\n received: " + headerSig +
257+ "\n computed: " + computedSig))
258+ default:
259+ panic("Unknown result value: " + result.String())
260+ }
261+}
262+
263+// fakeNetConn simulates an http.Request.Body buffer that will be consumed
264+// when it is read by the hmacauth.HmacAuth if not handled properly. See:
265+//
266+// https://github.com/18F/hmacauth/pull/4
267+type fakeNetConn struct {
268+ reqBody string
269+}
270+
271+func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
272+ if bodyLen := len(fnc.reqBody); bodyLen != 0 {
273+ copy(p, fnc.reqBody)
274+ fnc.reqBody = ""
275+ return bodyLen, io.EOF
276+ }
277+ return 0, io.EOF
278+}
279+
280+func TestSendAuthenticatedPostRequestToServer(t *testing.T) {
281+ key := "foobar"
282+ payload := `{ "hello": "world!" }`
283+
284+ auth := NewHmacAuth(crypto.SHA1, []byte(key), "X-Test-Signature", nil)
285+ authenticator := &SignatureAuthenticator{auth: auth}
286+ upstream := httptest.NewServer(
287+ http.HandlerFunc(authenticator.Authenticate))
288+
289+ req, err := http.NewRequest("POST", upstream.URL+"/foo/bar",
290+ io.NopCloser(&fakeNetConn{reqBody: payload}))
291+ if err != nil {
292+ panic(err)
293+ }
294+ auth.SignRequest(req)
295+ if response, err := http.DefaultClient.Do(req); err != nil {
296+ panic(err)
297+ } else {
298+ assert.Equal(t, response.StatusCode, http.StatusOK)
299+ responseBody, _ := io.ReadAll(response.Body)
300+ assert.Equal(t, "signatures match", string(responseBody))
301+ }
302+}
pkg/upstream/http.go+2−2
@@ -8,9 +8,9 @@ import (
8 "net/url"
8 "net/url"
9 "strings"
9 "strings"
10
10
11− "github.com/mbland/hmacauth"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
13+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
14)
14)
15
15
16const (
16const (
@@ -86,7 +86,7 @@ func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
86 // A scope should always be injected before this handler is called.
86 // A scope should always be injected before this handler is called.
87 scope.Upstream = h.upstream
87 scope.Upstream = h.upstream
88
88
89− // TODO (@NickMeves) - Deprecate GAP-Signature & remove GAP-Auth
89+ // TODO (@tuunit) - Deprecate GAP-Signature & remove GAP-Auth
90 if h.auth != nil {
90 if h.auth != nil {
91 req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
91 req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
92 h.auth.SignRequest(req)
92 h.auth.SignRequest(req)
pkg/validation/options.go+1−1
@@ -8,8 +8,8 @@ import (
8 "net/url"
8 "net/url"
9 "strings"
9 "strings"
10
10
11− "github.com/mbland/hmacauth"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
15 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
15 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"