git.iko.soy/typeable/oauth2-proxy
on master help sign in
fix: hmacauth dependency licensing issue (#3253)

* fix: upstream licensing issue by adopting hmacauth library and changing asserting library for its test cases Signed-off-by: Jan Larwig <jan@larwig.com> * fix: golang code quality and linting issues for hmacauth Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com>

commitfcf4e7947b86cda798c64bb9bb03ffeb3be90e3e authorJan Larwig <jan@larwig.com> dateSun 9 Nov 2025, 20:14 (1 year ago) parent082b49a
8 files changed+551−9
go.mod+1−3 go.sum+2−2 oauthproxy_test.go+1−1 pkg/authentication/hmacauth/LICENSE.md+5 pkg/authentication/hmacauth/hmacauth.go+237 pkg/authentication/hmacauth/hmacauth_test.go+302 pkg/upstream/http.go+2−2 pkg/validation/options.go+1−1
go.mod+1−3
@@ -20,7 +20,6 @@ require (
2020 github.com/google/uuid v1.6.0
2121 github.com/gorilla/mux v1.8.1
2222 github.com/justinas/alice v1.2.0
23− github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
2423 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
2524 github.com/onsi/ginkgo/v2 v2.23.4
2625 github.com/onsi/gomega v1.37.0
@@ -30,7 +29,7 @@ require (
3029 github.com/spf13/cast v1.9.2
3130 github.com/spf13/pflag v1.0.7
3231 github.com/spf13/viper v1.20.1
33− github.com/stretchr/testify v1.10.0
32+ github.com/stretchr/testify v1.11.1
3433 github.com/vmihailenco/msgpack/v5 v5.4.1
3534 golang.org/x/crypto v0.40.0
3635 golang.org/x/net v0.42.0
@@ -45,7 +44,6 @@ require (
4544 cloud.google.com/go/auth v0.16.2 // indirect
4645 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
4746 github.com/beorn7/perks v1.0.1 // indirect
48− github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
4947 github.com/cespare/xxhash/v2 v2.3.0 // indirect
5048 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
5149 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=
1818github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
1919github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
2020github.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=
2321github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
2422github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
2523github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -142,6 +140,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
142140github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
143141github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
144142github.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=
145145github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
146146github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
147147github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
oauthproxy_test.go+1−1
@@ -15,9 +15,9 @@ import (
1515 "time"
1616
1717 "github.com/coreos/go-oidc/v3/oidc"
18− "github.com/mbland/hmacauth"
1918 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
2019 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
20+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
2121 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
2222 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
2323 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 (
88 "net/url"
99 "strings"
1010
11− "github.com/mbland/hmacauth"
1211 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1312 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
13+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
1414)
1515
1616const (
@@ -86,7 +86,7 @@ func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
8686 // A scope should always be injected before this handler is called.
8787 scope.Upstream = h.upstream
8888
89− // TODO (@NickMeves) - Deprecate GAP-Signature & remove GAP-Auth
89+ // TODO (@tuunit) - Deprecate GAP-Signature & remove GAP-Auth
9090 if h.auth != nil {
9191 req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
9292 h.auth.SignRequest(req)
pkg/validation/options.go+1−1
@@ -8,8 +8,8 @@ import (
88 "net/url"
99 "strings"
1010
11− "github.com/mbland/hmacauth"
1211 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
1313 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
1414 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1515 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"