github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/gateway/sig/sig.go (about)

     1  package sig
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"regexp"
    10  	"strings"
    11  	"unicode/utf8"
    12  
    13  	"github.com/treeverse/lakefs/pkg/auth/model"
    14  	gwErrors "github.com/treeverse/lakefs/pkg/gateway/errors"
    15  )
    16  
    17  var (
    18  	ErrHeaderMalformed = errors.New("header malformed")
    19  
    20  	// reservedObjectNames - if object matches reserved string, no need to encode them
    21  	reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
    22  )
    23  
    24  // taken from https://github.com/minio/minio-go/blob/master/pkg/s3utils/utils.go
    25  /*
    26   * MinIO Go Library for Amazon S3 Compatible Cloud Storage
    27   * Copyright 2015-2017 MinIO, Inc.
    28   *
    29   * Licensed under the Apache License, Version 2.0 (the "License");
    30   * you may not use this file except in compliance with the License.
    31   * You may obtain a copy of the License at
    32   *
    33   *     http://www.apache.org/licenses/LICENSE-2.0
    34   *
    35   * Unless required by applicable law or agreed to in writing, software
    36   * distributed under the License is distributed on an "AS IS" BASIS,
    37   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    38   * See the License for the specific language governing permissions and
    39   * limitations under the License.
    40   */
    41  
    42  // EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
    43  // This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
    44  // non english characters cannot be parsed due to the nature in which url.Encode() is written
    45  // This function on the other hand is a direct replacement for url.Encode() technique to support
    46  // pretty much every UTF-8 character.
    47  func EncodePath(pathName string) string {
    48  	if reservedObjectNames.MatchString(pathName) {
    49  		return pathName
    50  	}
    51  	var encodedPathname string
    52  	for _, s := range pathName {
    53  		if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
    54  			encodedPathname += string(s)
    55  			continue
    56  		}
    57  		switch s {
    58  		case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
    59  			encodedPathname += string(s)
    60  			continue
    61  		default:
    62  			runeLen := utf8.RuneLen(s)
    63  			if runeLen < 0 {
    64  				// if utf8 cannot convert return the same string as is
    65  				return pathName
    66  			}
    67  			u := make([]byte, runeLen)
    68  			utf8.EncodeRune(u, s)
    69  			for _, r := range u {
    70  				h := hex.EncodeToString([]byte{r})
    71  				encodedPathname += "%" + strings.ToUpper(h)
    72  			}
    73  		}
    74  	}
    75  	return encodedPathname
    76  }
    77  
    78  type SigContext interface {
    79  	GetAccessKeyID() string
    80  }
    81  
    82  type SigAuthenticator interface {
    83  	Parse() (SigContext, error)
    84  	Verify(*model.Credential) error
    85  }
    86  
    87  type chainedAuthenticator struct {
    88  	methods []SigAuthenticator
    89  	chosen  SigAuthenticator
    90  }
    91  
    92  func ChainedAuthenticator(methods ...SigAuthenticator) SigAuthenticator {
    93  	return &chainedAuthenticator{methods, nil}
    94  }
    95  
    96  func (c *chainedAuthenticator) Parse() (SigContext, error) {
    97  	for _, method := range c.methods {
    98  		sigContext, err := method.Parse()
    99  		if err == nil {
   100  			c.chosen = method
   101  			return sigContext, nil
   102  		}
   103  	}
   104  	return nil, gwErrors.ErrMissingFields
   105  }
   106  
   107  func Equal(sig1, sig2 []byte) bool {
   108  	return hmac.Equal(sig1, sig2)
   109  }
   110  
   111  func (c *chainedAuthenticator) Verify(creds *model.Credential) error {
   112  	return c.chosen.Verify(creds)
   113  }
   114  
   115  func (c *chainedAuthenticator) String() string {
   116  	if c.chosen == nil {
   117  		return "chained authenticator"
   118  	}
   119  	return fmt.Sprintf("%s", c.chosen)
   120  }
   121  
   122  func IsAWSSignedRequest(req *http.Request) bool {
   123  	// headers first
   124  	headers := req.Header
   125  	v4Value := headers.Get(v4SignatureHeader)
   126  	if len(v4Value) > 0 {
   127  		return true
   128  	}
   129  	v4AuthHeader := headers.Get(V4authHeaderName)
   130  	if strings.HasPrefix(v4AuthHeader, "AWS4") {
   131  		return true
   132  	}
   133  	v2Value := headers.Get(v2authHeaderName)
   134  	if strings.HasPrefix(v2Value, "AWS ") {
   135  		return true
   136  	}
   137  
   138  	// then request params
   139  	queryParams := req.URL.Query()
   140  	// sigv2: https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
   141  	if len(queryParams.Get("AWSAccessKeyId")) > 0 {
   142  		return true
   143  	}
   144  	// sigv4: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
   145  	if len(queryParams.Get("X-Amz-Credential")) > 0 {
   146  		return true
   147  	}
   148  	return false
   149  }