github.com/google/osv-scalibr@v0.4.1/veles/secrets/awsaccesskey/validator.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 awsaccesskey
    16  
    17  import (
    18  	"context"
    19  	"encoding/xml"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"strings"
    24  
    25  	"github.com/google/osv-scalibr/veles"
    26  	"github.com/google/osv-scalibr/veles/secrets/common/awssignerv4"
    27  )
    28  
    29  const (
    30  	// CodeSignatureDoesNotMatch is returned by AWS if a request and the signature don't match
    31  	CodeSignatureDoesNotMatch = "SignatureDoesNotMatch"
    32  	// CodeAccessDenied is returned by AWS if the user doesn't have access to a resource
    33  	CodeAccessDenied = "AccessDenied"
    34  )
    35  
    36  // HTTPAwsSignerV4 defines the interface for signing HTTP requests using
    37  // the AWS Signature Version 4 signing process.
    38  type HTTPAwsSignerV4 interface {
    39  	Sign(req *http.Request, accessKey, secretKey string) error
    40  }
    41  
    42  // Validator is a Veles Validator for Google Cloud Storage HMAC keys
    43  type Validator struct {
    44  	client *http.Client
    45  	signer HTTPAwsSignerV4
    46  }
    47  
    48  // SetHTTPClient configures the http.Client that the Validator uses.
    49  func (v *Validator) SetHTTPClient(cli *http.Client) {
    50  	v.client = cli
    51  }
    52  
    53  // SetSigner configures HTTPSignerV4 that the Validator uses.
    54  func (v *Validator) SetSigner(signer HTTPAwsSignerV4) {
    55  	v.signer = signer
    56  }
    57  
    58  // NewValidator creates a new Validator with the given ValidatorOptions.
    59  func NewValidator() *Validator {
    60  	return &Validator{
    61  		client: http.DefaultClient,
    62  		signer: awssignerv4.New(awssignerv4.Config{
    63  			Service: "sts", Region: "us-east-1",
    64  			SignedHeaders: []string{
    65  				"amz-sdk-invocation-id", "amz-sdk-request", "host", "x-amz-content-sha256", "x-amz-date",
    66  			},
    67  		}),
    68  	}
    69  }
    70  
    71  // Validate checks whether the given AWS access key and secret are valid
    72  // using the GetCallerIdentity api call
    73  func (v *Validator) Validate(ctx context.Context, key Credentials) (veles.ValidationStatus, error) {
    74  	req, err := http.NewRequestWithContext(
    75  		ctx, http.MethodPost,
    76  		"https://sts.us-east-1.amazonaws.com/",
    77  		io.NopCloser(strings.NewReader("Action=GetCallerIdentity&Version=2011-06-15")),
    78  	)
    79  	if err != nil {
    80  		return veles.ValidationFailed, fmt.Errorf("building failed: %w", err)
    81  	}
    82  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    83  	req.Header.Set("User-Agent", "osv-scalibr")
    84  	req.Header.Set("Host", "sts.us-east-1.amazonaws.com")
    85  	req.Header.Set("Accept-Encoding", "gzip")
    86  
    87  	if err := v.signer.Sign(req, key.AccessID, key.Secret); err != nil {
    88  		return veles.ValidationFailed, fmt.Errorf("signing failed: %w", err)
    89  	}
    90  
    91  	rsp, err := v.client.Do(req)
    92  	if err != nil {
    93  		return veles.ValidationFailed, fmt.Errorf("POST failed: %w", err)
    94  	}
    95  	defer rsp.Body.Close()
    96  
    97  	if rsp.StatusCode == http.StatusOK {
    98  		return veles.ValidationValid, nil
    99  	}
   100  
   101  	type errorResponse struct {
   102  		Error struct {
   103  			Code string `xml:"Code"`
   104  		} `xml:"Error"`
   105  	}
   106  
   107  	errResp := errorResponse{}
   108  	if err := xml.NewDecoder(rsp.Body).Decode(&errResp); err != nil {
   109  		return veles.ValidationFailed, fmt.Errorf("failed to parse the response body: %w", err)
   110  	}
   111  
   112  	switch errResp.Error.Code {
   113  	case CodeSignatureDoesNotMatch:
   114  		// Signature mismatch => credentials invalid
   115  		return veles.ValidationInvalid, nil
   116  	case CodeAccessDenied:
   117  		// Signature valid, but account lacks access
   118  		return veles.ValidationValid, nil
   119  	default:
   120  		// Unexpected error response
   121  		return veles.ValidationFailed, fmt.Errorf("unknown error code: %q", errResp.Error.Code)
   122  	}
   123  }