github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/gateway/sig/sig_test.go (about) 1 package sig_test 2 3 import ( 4 "errors" 5 "fmt" 6 "math/rand" 7 "net/http" 8 "net/url" 9 "testing" 10 "time" 11 12 "github.com/minio/minio-go/v7/pkg/s3utils" 13 "github.com/minio/minio-go/v7/pkg/signer" 14 "github.com/treeverse/lakefs/pkg/auth/model" 15 gwErrors "github.com/treeverse/lakefs/pkg/gateway/errors" 16 "github.com/treeverse/lakefs/pkg/gateway/sig" 17 "github.com/treeverse/lakefs/pkg/testutil" 18 ) 19 20 func makeRequest(t *testing.T, headers map[string]string, query map[string]string) *http.Request { 21 r, err := http.NewRequest("GET", "https://example.com", nil) 22 if err != nil { 23 t.Fatal(err) 24 } 25 for k, v := range headers { 26 r.Header.Set(k, v) 27 } 28 q := r.URL.Query() 29 for k, v := range query { 30 q.Add(k, v) 31 } 32 r.URL.RawQuery = q.Encode() 33 return r 34 } 35 36 func TestIsAWSSignedRequest(t *testing.T) { 37 t.Parallel() 38 cases := []struct { 39 Name string 40 Want bool 41 Headers map[string]string 42 Query map[string]string 43 }{ 44 {Name: "no sig", Want: false}, 45 {Name: "non aws auth header", Want: false, Headers: map[string]string{"Authorization": "Basic dXNlcjpwYXNzd29yZA=="}}, 46 {Name: "v2 auth header", Want: true, Headers: map[string]string{"Authorization": "AWS wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"}}, 47 {Name: "v2 auth query param", Want: true, Query: map[string]string{"AWSAccessKeyId": "bPxRfiCYEXAMPLEKEY"}}, 48 {Name: "v4 auth header", Want: true, Headers: map[string]string{"Authorization": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"}}, 49 {Name: "v4 auth query param", Want: true, Query: map[string]string{"X-Amz-Credential": "bPxRfiCYEXAMPLEKEY"}}, 50 } 51 52 for _, tc := range cases { 53 t.Run(tc.Name, func(t *testing.T) { 54 r := makeRequest(t, tc.Headers, tc.Query) 55 got := sig.IsAWSSignedRequest(r) 56 if got != tc.Want { 57 t.Fatalf("IsAWSSignedRequest with %s: got %v, expected %v", tc.Name, got, tc.Want) 58 } 59 }) 60 } 61 } 62 63 type ( 64 Signer func(req http.Request) *http.Request 65 Verifier func(req *http.Request) error 66 ) 67 68 type Style string 69 70 func MakeV2Signer(keyID, secretKey string, style Style) Signer { 71 return func(req http.Request) *http.Request { 72 return signer.SignV2(req, keyID, secretKey, style == "host") 73 } 74 } 75 76 func MakeV4Signer(keyID, secretKey, location string) Signer { 77 return func(req http.Request) *http.Request { 78 return signer.SignV4(req, keyID, secretKey, "", location) 79 } 80 } 81 82 func MakeV2Verifier(keyID, secretKey, bareDomain string) Verifier { 83 return func(req *http.Request) error { 84 authenticator := sig.NewV2SigAuthenticator(req, bareDomain) 85 _, err := authenticator.Parse() 86 if err != nil { 87 return fmt.Errorf("sigV2 parse failed: %w", err) 88 } 89 return authenticator.Verify( 90 &model.Credential{ 91 BaseCredential: model.BaseCredential{ 92 AccessKeyID: keyID, 93 SecretAccessKey: secretKey, 94 }, 95 }, 96 ) 97 } 98 } 99 100 func MakeV4Verifier(keyID, secretKey string) Verifier { 101 return func(req *http.Request) error { 102 authenticator := sig.NewV4Authenticator(req) 103 _, err := authenticator.Parse() 104 if err != nil { 105 return fmt.Errorf("sigV4 parse failed: %w", err) 106 } 107 return authenticator.Verify( 108 &model.Credential{BaseCredential: model.BaseCredential{AccessKeyID: keyID, SecretAccessKey: secretKey}}, 109 ) 110 } 111 } 112 113 func MakeHeader(m map[string]string) http.Header { 114 ret := http.Header{} 115 for k, v := range m { 116 ret.Add(k, v) 117 } 118 return ret 119 } 120 121 const ( 122 keyID = "AKIAIOSFODNN7EXAMPLE" 123 secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 124 domain = "s3.lakefs.test" 125 location = "lu-alpha-1" 126 ) 127 128 var date = time.Unix(1631523198, 0) 129 130 type SignCase struct { 131 Name string 132 Signer Signer 133 Verifier Verifier 134 Style Style 135 } 136 137 var signatures = []SignCase{ 138 { 139 Name: "V2Host", 140 Signer: MakeV2Signer(keyID, secretKey, "host"), 141 Verifier: MakeV2Verifier(keyID, secretKey, domain), 142 Style: "host", 143 }, { 144 Name: "V2Path", 145 Signer: MakeV2Signer(keyID, secretKey, "path"), 146 Verifier: MakeV2Verifier(keyID, secretKey, domain), 147 Style: "path", 148 }, { 149 Name: "V4Host", 150 Signer: MakeV4Signer(keyID, secretKey, location), 151 Verifier: MakeV4Verifier(keyID, secretKey), 152 Style: "host", 153 }, { 154 Name: "V4Path", 155 Signer: MakeV4Signer(keyID, secretKey, location), 156 Verifier: MakeV4Verifier(keyID, secretKey), 157 Style: "path", 158 }, 159 } 160 161 func TestAWSSigVerify(t *testing.T) { 162 t.Parallel() 163 const ( 164 numRounds = 100 165 seed = 20210913 166 pathLength = 900 167 bucket = "my-bucket" 168 ) 169 methods := []string{"GET", "PUT", "DELETE", "PATCH"} 170 171 for _, s := range signatures { 172 t.Run("Sig"+s.Name, func(t *testing.T) { 173 host := domain 174 if s.Style == "host" { 175 host = fmt.Sprintf("%s.%s", bucket, domain) 176 } 177 178 r := rand.New(rand.NewSource(seed)) 179 for i := 0; i < numRounds; i++ { 180 path := s3utils.EncodePath("my-branch/ariels/x/" + testutil.RandomString(r, pathLength)) 181 bucketURL := &url.URL{ 182 Scheme: "s3", 183 Host: bucket, 184 Path: path, 185 // No way to construct possibly-equivalent forms, so let 186 // URL construct The Right RawPath. 187 RawPath: "", 188 } 189 req := http.Request{ 190 Method: methods[r.Intn(len(methods))], 191 Host: host, 192 URL: bucketURL, 193 Header: MakeHeader(map[string]string{ 194 "Date": date.Format(http.TimeFormat), 195 "x-amz-date": date.Format("20060102T150405Z"), 196 "Content-Md5": "deadbeef", 197 "Content-Type": "application/binary", 198 }), 199 } 200 signedReq := s.Signer(req) 201 err := s.Verifier(signedReq) 202 if err != nil { 203 errText := err.Error() 204 var apiErr gwErrors.APIErrorCode 205 if errors.As(err, &apiErr) { 206 errText = apiErr.ToAPIErr().Description 207 } 208 t.Errorf("Sign and verify %s: %s", bucketURL.String(), errText) 209 } 210 } 211 }) 212 } 213 }