github.com/google/osv-scalibr@v0.4.1/veles/secrets/gcshmackey/validator_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gcshmackey_test 16 17 import ( 18 "io" 19 "net/http" 20 "net/http/httptest" 21 "net/url" 22 "strings" 23 "testing" 24 25 "github.com/google/osv-scalibr/veles" 26 "github.com/google/osv-scalibr/veles/secrets/gcshmackey" 27 ) 28 29 type fakeSigner struct{} 30 31 func (n fakeSigner) Sign(r *http.Request, accessID string, secret string) error { 32 r.Header.Set("Authorization", "Signature="+accessID+":"+secret) 33 return nil 34 } 35 36 type mockRoundTripper struct { 37 url string 38 } 39 40 func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 41 if req.URL.Host == "storage.googleapis.com" { 42 testURL, _ := url.Parse(m.url) 43 req.URL.Scheme = testURL.Scheme 44 req.URL.Host = testURL.Host 45 } 46 return http.DefaultTransport.RoundTrip(req) 47 } 48 49 // mockS3Server returns an httptest.Server that simulates an S3 ListBuckets endpoint. 50 // It accepts only the "testsecret" key; any other secret yields SignatureDoesNotMatch. 51 func mockS3Server(signature string, denied bool) func() *httptest.Server { 52 return func() *httptest.Server { 53 handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 54 // Only handle ListBuckets (GET /) 55 if req.Method != http.MethodGet || req.URL.Path != "/" { 56 http.Error(w, "not found", http.StatusNotFound) 57 return 58 } 59 60 if !strings.Contains(req.Header.Get("Authorization"), signature) { 61 w.WriteHeader(http.StatusForbidden) 62 _, _ = io.WriteString(w, `<?xml version='1.0' encoding='UTF-8'?> 63 <Error> 64 <Code>SignatureDoesNotMatch</Code> 65 <Message>Access denied.</Message> 66 <Details>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Details> 67 <StringToSign>**REDACTED***</StringToSign> 68 <CanonicalRequest>**REDACTED***</CanonicalRequest> 69 </Error>`) 70 return 71 } 72 73 if denied { 74 w.WriteHeader(http.StatusForbidden) 75 _, _ = io.WriteString(w, `<?xml version='1.0' encoding='UTF-8'?> 76 <Error> 77 <Code>AccessDenied</Code> 78 <Message>Access denied.</Message> 79 <Details>**SERVICE-ACCOUNT-EMAIL-REDACTED** does not have storage.buckets.list access to the Google Cloud project. Permission 'storage.buckets.list' denied on resource (or it may not exist).</Details> 80 </Error>`) 81 return 82 } 83 84 _, _ = io.WriteString(w, `<?xml version='1.0' encoding='UTF-8'?> 85 <ListAllMyBucketsResult xmlns='http://doc.s3.amazonaws.com/2006-03-01'> 86 <Buckets/> 87 </ListAllMyBucketsResult>`) 88 w.WriteHeader(http.StatusOK) 89 }) 90 91 return httptest.NewServer(handler) 92 } 93 } 94 95 const ( 96 exampleAccessID = "GOOGerkjf4f034" 97 correctSecret = "testsecret" 98 badSecret = "badSecret" 99 correctSignature = exampleAccessID + ":" + correctSecret 100 ) 101 102 func TestValidator(t *testing.T) { 103 cases := []struct { 104 name string 105 key gcshmackey.HMACKey 106 want veles.ValidationStatus 107 server func() *httptest.Server 108 }{ 109 { 110 name: "correct_secret", 111 key: gcshmackey.HMACKey{ 112 AccessID: exampleAccessID, 113 Secret: correctSecret, 114 }, 115 want: veles.ValidationValid, 116 server: mockS3Server(correctSignature, false), 117 }, 118 { 119 name: "correct_secret,_access_denied", 120 key: gcshmackey.HMACKey{ 121 AccessID: exampleAccessID, 122 Secret: correctSecret, 123 }, 124 want: veles.ValidationValid, 125 server: mockS3Server(correctSignature, true), 126 }, 127 { 128 name: "bad_secret", 129 key: gcshmackey.HMACKey{ 130 AccessID: exampleAccessID, 131 Secret: badSecret, 132 }, 133 want: veles.ValidationInvalid, 134 server: mockS3Server(correctSignature, true), 135 }, 136 } 137 138 for _, tc := range cases { 139 t.Run(tc.name, func(t *testing.T) { 140 srv := tc.server() 141 client := &http.Client{ 142 Transport: &mockRoundTripper{url: srv.URL}, 143 } 144 145 validator := gcshmackey.NewValidator() 146 validator.SetHTTPClient(client) 147 validator.SetSigner(fakeSigner{}) 148 149 got, err := validator.Validate(t.Context(), tc.key) 150 if err != nil { 151 t.Errorf("Validate() error: %v, want nil", err) 152 } 153 if got != tc.want { 154 t.Errorf("Validate() = %q, want %q", got, tc.want) 155 } 156 }) 157 } 158 }