Skip to content

Commit

Permalink
Support signing sigv4 requests (#23)
Browse files Browse the repository at this point in the history
* add sigv4 package - untested

* sigv4: remove validation, unused and unnecessary

* add sigv4 processor

* refactor: remove sigv4 package, merge all into processor function

* bump macaroon lib

* sigv4: parse correct timestamp format. no idea what i was thinking

* sigv4: strip headers before signing requests, to fix signature mismatch

* sigv4: add error handling for malformed or missing auth header

---------

Co-authored-by: btoews <[email protected]>
  • Loading branch information
alichay and btoews authored Aug 26, 2024
1 parent 68cc350 commit a17a2e0
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ go 1.20

require (
github.com/alecthomas/assert/v2 v2.3.0
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027
github.com/sirupsen/logrus v1.9.3
github.com/superfly/macaroon v0.2.14-0.20240718172852-139f90b76537
github.com/superfly/macaroon v0.2.14-0.20240819201738-61a02aa53648
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
)

require (
github.com/alecthomas/repr v0.2.0 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVd
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -26,6 +30,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/superfly/macaroon v0.2.14-0.20240718172852-139f90b76537 h1:xL2tIkau3Dr3dd4WOLbGz14kRcF49x15bVIMdOkLTyI=
github.com/superfly/macaroon v0.2.14-0.20240718172852-139f90b76537/go.mod h1:Kt6/EdSYfFjR4GIe+erMwcJgU8iMu1noYVceQ5dNdKo=
github.com/superfly/macaroon v0.2.14-0.20240819201738-61a02aa53648 h1:YQG1v1QcTFQxJureNBcbtxosZ98u78ceUNCDQgI/vgM=
github.com/superfly/macaroon v0.2.14-0.20240819201738-61a02aa53648/go.mod h1:Kt6/EdSYfFjR4GIe+erMwcJgU8iMu1noYVceQ5dNdKo=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
Expand Down
97 changes: 97 additions & 0 deletions processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
"net/http"
"net/textproto"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"golang.org/x/exp/slices"
)

Expand Down Expand Up @@ -145,6 +148,100 @@ func (c *OAuthProcessorConfig) Processor(params map[string]string) (RequestProce
}, nil
}

type Sigv4ProcessorConfig struct {
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
}

var _ ProcessorConfig = (*Sigv4ProcessorConfig)(nil)

func (c *Sigv4ProcessorConfig) Processor(params map[string]string) (RequestProcessor, error) {

if len(c.AccessKey) == 0 {
return nil, errors.New("missing access key")
}
if len(c.SecretKey) == 0 {
return nil, errors.New("missing secret key")
}

return func(r *http.Request) error {
// NOTE: Sigv4 has defenses against request forgery and reuse.
// This does *not* make those guarantees, and likely can not.

var (
service string
region string
date time.Time
err error
)

// Parse the auth header to get the service, region, and date
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return errors.New("expected request to contain an Authorization header")
}

for _, section := range strings.Split(authHeader, " ") {
section = strings.TrimRight(section, ",")
keyValuePair := strings.SplitN(section, "=", 2)
if len(keyValuePair) != 2 {
continue
}

if keyValuePair[0] == "Credential" {
credParts := strings.Split(keyValuePair[1], "/")
if len(credParts) != 5 {
return errors.New("invalid credential in auth header")
}

dateStr := credParts[1]
date, err = time.Parse("20060102", dateStr)
if err != nil {
return err
}

service = credParts[2]
region = credParts[3]
break
}
}
if timestamp := r.Header.Get("X-Amz-Date"); timestamp != "" {
date, err = time.Parse("20060102T150405Z", timestamp)
if err != nil {
return err
}
}
if service == "" || region == "" || date.IsZero() {
return errors.New("expected valid sigv4 authentication header in request to tokenizer")
}

// Strip the Authorization header from the request
r.Header.Del("Authorization")

credentials := aws.Credentials{
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
}

// HACK: We have to strip the filtered headers *before* the request gets signed,
// since sigv4 expects a signature of all the request's headers.
for _, h := range FilteredHeaders {
r.Header.Del(h)
}
// Remove headers that goproxy will strip out later anyway. Otherwise, the header signature check will fail.
// https://github.com/elazarl/goproxy/blob/8b0c205063807802a7ac1d75351a90172a9c83fb/proxy.go#L87-L92
r.Header.Del("Accept-Encoding")
r.Header.Del("Proxy-Connection")
r.Header.Del("Proxy-Authenticate")
r.Header.Del("Proxy-Authorization")

signer := v4.NewSigner()
err = signer.SignHTTP(r.Context(), credentials, r, r.Header.Get("X-Amz-Content-Sha256"), service, region, date)

return err
}, nil
}

// A helper type to be embedded in RequestProcessors wanting to use the `fmt` config/param.
type FmtProcessor struct {
Fmt string `json:"fmt,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type wireSecret struct {
*InjectProcessorConfig `json:"inject_processor,omitempty"`
*InjectHMACProcessorConfig `json:"inject_hmac_processor,omitempty"`
*OAuthProcessorConfig `json:"oauth2_processor,omitempty"`
*Sigv4ProcessorConfig `json:"sigv4_processor,omitempty"`
*BearerAuthConfig `json:"bearer_auth,omitempty"`
*MacaroonAuthConfig `json:"macaroon_auth,omitempty"`
*FlyioMacaroonAuthConfig `json:"flyio_macaroon_auth,omitempty"`
Expand Down Expand Up @@ -81,6 +82,8 @@ func (s *Secret) MarshalJSON() ([]byte, error) {
ws.InjectHMACProcessorConfig = p
case *OAuthProcessorConfig:
ws.OAuthProcessorConfig = p
case *Sigv4ProcessorConfig:
ws.Sigv4ProcessorConfig = p
default:
return nil, errors.New("bad processor config")
}
Expand Down Expand Up @@ -121,6 +124,10 @@ func (s *Secret) UnmarshalJSON(b []byte) error {
np += 1
s.ProcessorConfig = ws.OAuthProcessorConfig
}
if ws.Sigv4ProcessorConfig != nil {
np += 1
s.ProcessorConfig = ws.Sigv4ProcessorConfig
}
if np != 1 {
return errors.New("bad processor config")
}
Expand Down

0 comments on commit a17a2e0

Please sign in to comment.