Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement support for http digest auth (resolve #352) #553

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 31 additions & 6 deletions config/http_config.go
Expand Up @@ -30,6 +30,7 @@ import (
"sync"
"time"

"github.com/icholy/digest"
"github.com/mwitkow/go-conntrack"
"golang.org/x/net/http/httpproxy"
"golang.org/x/net/http2"
Expand Down Expand Up @@ -143,6 +144,11 @@ func (a *BasicAuth) SetDirectory(dir string) {
a.UsernameFile = JoinDir(dir, a.UsernameFile)
}

type DigestAuth struct {
Username string `yaml:"username" json:"username"`
Password Secret `yaml:"password,omitempty" json:"password,omitempty"`
}

// Authorization contains HTTP authorization credentials.
type Authorization struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Expand Down Expand Up @@ -288,7 +294,8 @@ func LoadHTTPConfigFile(filename string) (*HTTPClientConfig, []byte, error) {
// HTTPClientConfig configures an HTTP client.
type HTTPClientConfig struct {
// The HTTP basic authentication credentials for the targets.
BasicAuth *BasicAuth `yaml:"basic_auth,omitempty" json:"basic_auth,omitempty"`
BasicAuth *BasicAuth `yaml:"basic_auth,omitempty" json:"basic_auth,omitempty"`
DigestAuth *DigestAuth `yaml:"digest_auth,omitempty" json:"digest_auth,omitempty"`
// The HTTP authorization credentials for the targets.
Authorization *Authorization `yaml:"authorization,omitempty" json:"authorization,omitempty"`
// The OAuth2 client credentials used to fetch a token for the targets.
Expand Down Expand Up @@ -333,8 +340,8 @@ func (c *HTTPClientConfig) Validate() error {
if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 {
return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured")
}
if (c.BasicAuth != nil || c.OAuth2 != nil) && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) {
return fmt.Errorf("at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured")
if (c.BasicAuth != nil || c.OAuth2 != nil || c.DigestAuth != nil) && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) {
return fmt.Errorf("at most one of basic_auth, digest_auth, oauth2, bearer_token & bearer_token_file must be configured")
}
if c.BasicAuth != nil && (string(c.BasicAuth.Username) != "" && c.BasicAuth.UsernameFile != "") {
return fmt.Errorf("at most one of basic_auth username & username_file must be configured")
Expand All @@ -356,8 +363,8 @@ func (c *HTTPClientConfig) Validate() error {
if strings.ToLower(c.Authorization.Type) == "basic" {
return fmt.Errorf(`authorization type cannot be set to "basic", use "basic_auth" instead`)
}
if c.BasicAuth != nil || c.OAuth2 != nil {
return fmt.Errorf("at most one of basic_auth, oauth2 & authorization must be configured")
if c.BasicAuth != nil || c.OAuth2 != nil || c.DigestAuth != nil {
return fmt.Errorf("at most one of basic_auth, digest_auth, oauth2 & authorization must be configured")
Comment on lines +366 to +367
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these scattered checks to ensure only one type of auth is enabled bug me: more places to go wrong.
Maybe refactor them to a function that ensures only one type of auth is configured, and drop them in as needed.

auths_enabled := []bool{
  c.BasicAuth != nil,
  c.OAuth2 != nil,
  c.DigestAuth != nil,
  c.Authorization != nil,
}
if len(auths_enabled) > 1 {
  //  error
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree! I found these checks a bot strange as well and would vote for refactoring them (willing to do that!). Only wondering if this refactoring shall be included here or rather as part of another PR, so we can get this one merged preferably soon? Let me know what you think is better.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not the maintainer of this codebase, but if I had a choice, I'd say should be a separate PR that merges before this one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is a maintainer and could eventually be responsible for merging my PR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry but I'm not maintainer here, just a contributor

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sorry to bother you then. Who can I tag here to finally (!) get this finished?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello??? @SuperQ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?????

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This took me like half a day of work. Sorry, but I find it quite disrespectful that none of the maintainer even only bothers to reply.

}
} else {
if len(c.BearerToken) > 0 {
Expand All @@ -373,7 +380,10 @@ func (c *HTTPClientConfig) Validate() error {
}
if c.OAuth2 != nil {
if c.BasicAuth != nil {
return fmt.Errorf("at most one of basic_auth, oauth2 & authorization must be configured")
return fmt.Errorf("at most one of basic_auth, digest_auth, oauth2 & authorization must be configured")
}
if c.DigestAuth != nil {
return fmt.Errorf("at most one of basic_auth, digest_auth, oauth2 & authorization must be configured")
}
if len(c.OAuth2.ClientID) == 0 {
return fmt.Errorf("oauth2 client_id must be configured")
Expand All @@ -388,6 +398,9 @@ func (c *HTTPClientConfig) Validate() error {
return fmt.Errorf("at most one of oauth2 client_secret & client_secret_file must be configured")
}
}
if c.DigestAuth != nil && (c.BasicAuth != nil || c.Authorization != nil) {
return fmt.Errorf("at most one of basic_auth, digest_auth, oauth2 & authorization must be configured")
}
if err := c.ProxyConfig.Validate(); err != nil {
return err
}
Expand Down Expand Up @@ -563,6 +576,10 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.UsernameFile, cfg.BasicAuth.PasswordFile, rt)
}

if cfg.DigestAuth != nil {
rt = NewDigestAuthRoundTripper(cfg.DigestAuth.Username, cfg.DigestAuth.Password, rt)
}

if cfg.OAuth2 != nil {
rt = NewOAuth2RoundTripper(cfg.OAuth2, rt, &opts)
}
Expand Down Expand Up @@ -696,6 +713,14 @@ func (rt *basicAuthRoundTripper) CloseIdleConnections() {
}
}

func NewDigestAuthRoundTripper(username string, password Secret, rt http.RoundTripper) http.RoundTripper {
return &digest.Transport{
Username: username,
Password: string(password),
Transport: rt,
}
}

type oauth2RoundTripper struct {
config *OAuth2
rt http.RoundTripper
Expand Down
100 changes: 89 additions & 11 deletions config/http_config_test.go
Expand Up @@ -15,8 +15,10 @@ package config

import (
"context"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -77,7 +79,7 @@ var invalidHTTPClientConfigs = []struct {
},
{
httpClientConfigFile: "testdata/http.conf.empty.bad.yml",
errMsg: "at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured",
errMsg: "at most one of basic_auth, digest_auth, oauth2, bearer_token & bearer_token_file must be configured",
},
{
httpClientConfigFile: "testdata/http.conf.basic-auth.too-much.bad.yaml",
Expand All @@ -97,11 +99,15 @@ var invalidHTTPClientConfigs = []struct {
},
{
httpClientConfigFile: "testdata/http.conf.basic-auth-and-auth-creds.too-much.bad.yaml",
errMsg: "at most one of basic_auth, oauth2 & authorization must be configured",
errMsg: "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured",
},
{
httpClientConfigFile: "testdata/http.conf.basic-auth-and-oauth2.too-much.bad.yaml",
errMsg: "at most one of basic_auth, oauth2 & authorization must be configured",
errMsg: "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured",
},
{
httpClientConfigFile: "testdata/http.conf.basic-auth-and-digest.too-much.bad.yaml",
errMsg: "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured",
},
{
httpClientConfigFile: "testdata/http.conf.auth-creds-no-basic.bad.yaml",
Expand Down Expand Up @@ -312,6 +318,31 @@ func TestNewClientFromConfig(t *testing.T) {
fmt.Fprint(w, ExpectedMessage)
}
},
}, {
clientConfig: HTTPClientConfig{
BasicAuth: &BasicAuth{
Username: ExpectedUsername,
Password: ExpectedPassword,
},
TLSConfig: TLSConfig{
CAFile: TLSCAChainPath,
CertFile: ClientCertificatePath,
KeyFile: ClientKeyNoPassPath,
ServerName: "",
InsecureSkipVerify: false},
},
handler: func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
fmt.Fprintf(w, "The Authorization header wasn't set")
} else if ExpectedUsername != username {
fmt.Fprintf(w, "The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username)
} else if ExpectedPassword != password {
fmt.Fprintf(w, "The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password)
} else {
fmt.Fprint(w, ExpectedMessage)
}
},
}, {
clientConfig: HTTPClientConfig{
Authorization: &Authorization{
Expand All @@ -335,7 +366,7 @@ func TestNewClientFromConfig(t *testing.T) {
},
}, {
clientConfig: HTTPClientConfig{
BasicAuth: &BasicAuth{
DigestAuth: &DigestAuth{
Username: ExpectedUsername,
Password: ExpectedPassword,
},
Expand All @@ -347,14 +378,61 @@ func TestNewClientFromConfig(t *testing.T) {
InsecureSkipVerify: false},
},
handler: func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
fmt.Fprintf(w, "The Authorization header wasn't set")
} else if ExpectedUsername != username {
fmt.Fprintf(w, "The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username)
} else if ExpectedPassword != password {
fmt.Fprintf(w, "The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password)
// Example server response header:
// WWW-Authenticate: Digest realm="prometheus", nonce="43568ca162f46c3bcc57ecae193b3159", qop="auth", opaque="3bc9f19d8195721e24469ff255750f8c", algorithm=MD5, stale=FALSE
//
// Example client request header:
// Authorization: Digest username="foo", realm="prometheus", nonce="43568ca162f46c3bcc57ecae193b3159", uri="/", cnonce="NDA2M2JmYzQ2YTQ4OTQ0OTQ1NzE0NmI3ZmYyY2YyNzU=", nc=00000001, qop=auth, response="fe543d7eeb2d2f0aba8d100a1f076909", opaque="3bc9f19d8195721e24469ff255750f8c", algorithm=MD5

const (
nonce = "43568ca162f46c3bcc57ecae193b3159"
realm = "prometheus"
)

if authHeader := r.Header.Get("Authorization"); authHeader == "" {
// first request
w.Header().Set("www-authenticate", "Digest realm=\""+realm+"\", nonce=\""+nonce+"\", qop=\"auth\", opaque=\"3bc9f19d8195721e24469ff255750f8c\", algorithm=MD5, stale=FALSE")
w.WriteHeader(401)
Comment on lines +392 to +395
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we refactor this test to allow varying the inputs?
There's a couple of cases I think this needs to be able to test:

  • qop=auth-int
  • different algorithms
  • algorithm=(alg)-sess vs algorithm=(alg) (all the way to include cnonces)

Later on, if we can keep some state: nextnonce support

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review! I could include additional tests for different combinations of these values if requested. But I'm not sure if that's the way to go. The digest auth client library already includes tests for these (e.g. see here). While we surely want to test that digest auth works in general, I don't think it's our job to test the inner working of the library again.

} else {
// second, authenticated request
if !strings.HasPrefix(authHeader, "Digest") {
fmt.Fprint(w, "Request does not contain a valid digest auth header")
return
}

digestComponents := make(map[string]string)
for _, p := range strings.Split(authHeader, ", ")[1:] {
kvParts := strings.Split(p, "=")
digestComponents[kvParts[0]] = strings.TrimSpace(strings.Trim(kvParts[1], "\""))
}

if v := digestComponents["realm"]; v != realm {
fmt.Fprintf(w, "Digest auth with wrong realm (%s)", v)
return
}
if v := digestComponents["nonce"]; v != nonce {
fmt.Fprintf(w, "Digest auth with wrong nonce (%s)", v)
return
}

hashMD5 := func(s string) string {
hasher := md5.New()
hasher.Write([]byte(s))
return hex.EncodeToString(hasher.Sum(nil))
}

hash1Str := fmt.Sprintf("%s:%s:%s", ExpectedUsername, realm, ExpectedPassword)
hash1 := hashMD5(hash1Str)
hash2Str := fmt.Sprintf("GET:%s", digestComponents["uri"])
hash2 := hashMD5(hash2Str)
responseStr := fmt.Sprintf("%s:%s:%s:%s:%s:%s", hash1, nonce, digestComponents["nc"], digestComponents["cnonce"], digestComponents["qop"], hash2)
response := hashMD5(responseStr)

if response != digestComponents["response"] {
fmt.Fprintf(w, "Digest auth failed, response hashes didn't match")
return
}

fmt.Fprint(w, ExpectedMessage)
}
},
Expand Down
@@ -0,0 +1,6 @@
basic_auth:
username: user
password: foo
digest_auth:
username: user
password: foo
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -5,6 +5,7 @@ go 1.20
require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/go-kit/log v0.2.1
github.com/icholy/digest v0.1.22
github.com/julienschmidt/httprouter v1.3.0
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Expand Up @@ -16,8 +16,11 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/icholy/digest v0.1.22 h1:dRIwCjtAcXch57ei+F0HSb5hmprL873+q7PoVojdMzM=
github.com/icholy/digest v0.1.22/go.mod h1:uLAeDdWKIWNFMH0wqbwchbTQOmJWhzSnL7zmqSPqEEc=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
Expand All @@ -31,6 +34,8 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
Expand All @@ -41,17 +46,20 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand All @@ -60,6 +68,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
Expand All @@ -74,3 +83,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
1 change: 1 addition & 0 deletions sigv4/go.mod
Expand Up @@ -17,6 +17,7 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/icholy/digest v0.1.22 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/kr/text v0.2.0 // indirect
Expand Down