mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 10:06:14 +00:00
221 lines
5.8 KiB
Go
221 lines
5.8 KiB
Go
// Copyright 2023 The CubeFS Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package objectnode
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
v2PresignedQueries = []string{AWSAccessKeyID, Expires, Signature}
|
|
v4PresignedQueries = []string{XAmzAlgorithm, XAmzCredential, XAmzDate, XAmzExpires, XAmzSignedHeaders, XAmzSignature}
|
|
)
|
|
|
|
type QueryAuth struct {
|
|
SignatureInfo
|
|
|
|
query url.Values
|
|
request *http.Request
|
|
}
|
|
|
|
func NewQueryAuth(r *http.Request) (Auther, error) {
|
|
auth := &QueryAuth{
|
|
request: r,
|
|
query: r.URL.Query(),
|
|
}
|
|
auth.credential = new(Credential)
|
|
|
|
switch {
|
|
case auth.query.Get(AWSAccessKeyID) != "":
|
|
auth.version = signatureV2
|
|
auth.algorithm = signV2Algorithm
|
|
return auth, auth.parseSignV2()
|
|
case auth.query.Get(XAmzAlgorithm) != "":
|
|
if strings.HasPrefix(auth.query.Get(XAmzAlgorithm), signatureV4) {
|
|
auth.version = signatureV4
|
|
auth.algorithm = auth.query.Get(XAmzAlgorithm)
|
|
return auth, auth.parseSignV4()
|
|
}
|
|
return nil, ErrInvalidAuthQueryParams
|
|
default:
|
|
return nil, MissingSecurityElement
|
|
}
|
|
}
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
|
|
func (auth *QueryAuth) parseSignV2() error {
|
|
for _, query := range v2PresignedQueries {
|
|
var val string
|
|
if val = auth.query.Get(query); val == "" {
|
|
return ErrMissingAuthQueryParams
|
|
}
|
|
|
|
switch query {
|
|
case AWSAccessKeyID:
|
|
auth.credential.AccessKey = val
|
|
case Expires:
|
|
expires, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return ErrMalformedQueryExpires
|
|
}
|
|
auth.credential.Expires = expires
|
|
case Signature:
|
|
auth.signature = val
|
|
default:
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sigv4-query-string-auth.html
|
|
func (auth *QueryAuth) parseSignV4() error {
|
|
for _, query := range v4PresignedQueries {
|
|
var val string
|
|
if val = strings.TrimSpace(auth.query.Get(query)); val == "" {
|
|
return ErrMissingAuthQueryParams
|
|
}
|
|
|
|
switch query {
|
|
case XAmzDate:
|
|
if _, err := time.Parse(ISO8601Format, val); err != nil {
|
|
return ErrMalformedXAmzDate
|
|
}
|
|
auth.credential.TimeStamp = val
|
|
case XAmzExpires:
|
|
expires, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return ErrMalformedQueryExpires
|
|
}
|
|
if expires < MinSignatureExpires || expires > MaxSignatureExpires {
|
|
return ErrInvalidRangeExpires
|
|
}
|
|
auth.credential.Expires = expires
|
|
case XAmzSignature:
|
|
auth.signature = val
|
|
case XAmzCredential:
|
|
cred := strings.Split(auth.query.Get(XAmzCredential), "/")
|
|
if len(cred) != 5 {
|
|
return ErrMalformedCredential
|
|
}
|
|
auth.credential.AccessKey = cred[0]
|
|
auth.credential.Date = cred[1]
|
|
auth.credential.Region = cred[2]
|
|
auth.credential.Service = cred[3]
|
|
auth.credential.Request = cred[4]
|
|
case XAmzSignedHeaders:
|
|
auth.signedHeaders = strings.Split(val, ";")
|
|
default:
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (auth *QueryAuth) IsPresigned() bool {
|
|
return true
|
|
}
|
|
|
|
func (auth *QueryAuth) IsExpired() bool {
|
|
switch {
|
|
case auth.version == signatureV2:
|
|
return time.Now().UTC().Unix() > auth.credential.Expires
|
|
case auth.version == signatureV4:
|
|
ts, err := auth.credential.ParseTimeStamp()
|
|
if err != nil {
|
|
return true
|
|
}
|
|
now := time.Now().UTC()
|
|
ltime := ts.Add(-MaxRequestSkewedSeconds * time.Second)
|
|
rtime := ts.Add(time.Duration(auth.credential.Expires) * time.Second)
|
|
return !(now.After(ltime) && now.Before(rtime))
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (auth *QueryAuth) IsSkewed() bool {
|
|
return false
|
|
}
|
|
|
|
func (auth *QueryAuth) Version() string {
|
|
return auth.version
|
|
}
|
|
|
|
func (auth *QueryAuth) Algorithm() string {
|
|
return auth.algorithm
|
|
}
|
|
|
|
func (auth *QueryAuth) Signature() string {
|
|
return auth.signature
|
|
}
|
|
|
|
func (auth *QueryAuth) Credential() *Credential {
|
|
return auth.credential
|
|
}
|
|
|
|
func (auth *QueryAuth) SignedHeaders() []string {
|
|
return auth.signedHeaders
|
|
}
|
|
|
|
func (auth *QueryAuth) StringToSign() string {
|
|
return auth.stringToSign
|
|
}
|
|
|
|
func (auth *QueryAuth) CanonicalRequest() string {
|
|
return auth.canonicalRequest
|
|
}
|
|
|
|
func (auth *QueryAuth) SignatureMatch(secretKey string, wildcards Wildcards) bool {
|
|
switch auth.version {
|
|
case signatureV2:
|
|
return auth.signature == auth.buildSignatureV2(secretKey, wildcards)
|
|
case signatureV4:
|
|
return auth.signature == auth.buildSignatureV4(secretKey)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (auth *QueryAuth) buildSignatureV2(secretKey string, wildcards Wildcards) string {
|
|
req := auth.request
|
|
|
|
canonicalResource := buildCanonicalizedResourceV2(req, wildcards)
|
|
canonicalResourceQuery := buildCanonicalizedResourceQueryV2(canonicalResource, req.URL.Query())
|
|
expires := strconv.FormatInt(auth.credential.Expires, 10)
|
|
auth.stringToSign = buildStringToSignV2(req.Method, expires, canonicalResourceQuery, req.Header)
|
|
|
|
return calculateSignatureV2(secretKey, auth.stringToSign)
|
|
}
|
|
|
|
func (auth *QueryAuth) buildSignatureV4(secretKey string) string {
|
|
req := auth.request
|
|
|
|
auth.canonicalRequest = buildCanonicalRequest(req, auth.signedHeaders, true)
|
|
|
|
cred := auth.credential
|
|
signingKey := buildSigningKey(auth.version, secretKey, cred.Date, cred.Region, cred.Service, cred.Request)
|
|
scope := buildScope(cred.Date, cred.Region, cred.Service, cred.Request)
|
|
auth.stringToSign = buildStringToSign(auth.algorithm, cred.TimeStamp, scope, auth.canonicalRequest)
|
|
|
|
return calculateSignature(signingKey, auth.stringToSign)
|
|
}
|