Huawei Cloud Provider implementation (#241)
* add huawei CCI provider * add readme * add vender * add huawei provider mock test
This commit is contained in:
309
providers/huawei/auth/auth.go
Normal file
309
providers/huawei/auth/auth.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package auth
|
||||
|
||||
// HWS API Gateway Signature
|
||||
// Analog to AWS Signature Version 4, with some HWS specific parameters
|
||||
// Please refer to: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BasicDateFormat and BasicDateFormatShort define aws-date format
|
||||
const (
|
||||
BasicDateFormat = "20060102T150405Z"
|
||||
BasicDateFormatShort = "20060102"
|
||||
TerminationString = "sdk_request"
|
||||
Algorithm = "SDK-HMAC-SHA256"
|
||||
PreSKString = "SDK"
|
||||
HeaderXDate = "x-sdk-date"
|
||||
HeaderDate = "date"
|
||||
HeaderHost = "host"
|
||||
HeaderAuthorization = "Authorization"
|
||||
HeaderContentSha256 = "x-sdk-content-sha256"
|
||||
// todo: use the region and service.
|
||||
DefaultRegion = "default"
|
||||
DefaultService = "apigateway"
|
||||
)
|
||||
|
||||
func shouldEscape(c byte) bool {
|
||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '-' || c == '~' || c == '.' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func escape(s string) string {
|
||||
hexCount := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c) {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
|
||||
if hexCount == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)+2*hexCount)
|
||||
j := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case shouldEscape(c):
|
||||
t[j] = '%'
|
||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||
j += 3
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func hmacsha256(key []byte, data string) ([]byte, error) {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
if _, err := h.Write([]byte(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// Build a CanonicalRequest from a regular request string
|
||||
//
|
||||
// See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
// CanonicalRequest =
|
||||
// HTTPRequestMethod + '\n' +
|
||||
// CanonicalURI + '\n' +
|
||||
// CanonicalQueryString + '\n' +
|
||||
// CanonicalHeaders + '\n' +
|
||||
// SignedHeaders + '\n' +
|
||||
// HexEncode(Hash(RequestPayload))
|
||||
func CanonicalRequest(r *http.Request) (string, error) {
|
||||
var hexencode string
|
||||
var err error
|
||||
if hex := r.Header.Get(HeaderContentSha256); hex != "" {
|
||||
hexencode = hex
|
||||
} else {
|
||||
data, err := RequestPayload(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hexencode, err = HexEncodeSHA256Hash(data)
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, CanonicalURI(r), CanonicalQueryString(r), CanonicalHeaders(r), SignedHeaders(r), hexencode), err
|
||||
}
|
||||
|
||||
// CanonicalURI returns request uri
|
||||
func CanonicalURI(r *http.Request) string {
|
||||
pattens := strings.Split(r.URL.Path, "/")
|
||||
var uri []string
|
||||
for _, v := range pattens {
|
||||
switch v {
|
||||
case "":
|
||||
continue
|
||||
case ".":
|
||||
continue
|
||||
case "..":
|
||||
if len(uri) > 0 {
|
||||
uri = uri[:len(uri)-1]
|
||||
}
|
||||
default:
|
||||
uri = append(uri, escape(v))
|
||||
}
|
||||
}
|
||||
urlpath := "/"
|
||||
if len(uri) > 0 {
|
||||
urlpath = urlpath + strings.Join(uri, "/") + "/"
|
||||
}
|
||||
return urlpath
|
||||
}
|
||||
|
||||
// CanonicalQueryString
|
||||
func CanonicalQueryString(r *http.Request) string {
|
||||
var a []string
|
||||
for key, value := range r.URL.Query() {
|
||||
k := escape(key)
|
||||
for _, v := range value {
|
||||
var kv string
|
||||
if v == "" {
|
||||
kv = k
|
||||
} else {
|
||||
kv = fmt.Sprintf("%s=%s", k, escape(v))
|
||||
}
|
||||
a = append(a, kv)
|
||||
}
|
||||
}
|
||||
sort.Strings(a)
|
||||
query := strings.Join(a, "&")
|
||||
r.URL.RawQuery = query
|
||||
return query
|
||||
}
|
||||
|
||||
// CanonicalHeaders
|
||||
func CanonicalHeaders(r *http.Request) string {
|
||||
var a []string
|
||||
for key, value := range r.Header {
|
||||
sort.Strings(value)
|
||||
var q []string
|
||||
for _, v := range value {
|
||||
q = append(q, trimString(v))
|
||||
}
|
||||
a = append(a, strings.ToLower(key)+":"+strings.Join(q, ","))
|
||||
}
|
||||
a = append(a, HeaderHost+":"+r.Host)
|
||||
sort.Strings(a)
|
||||
return fmt.Sprintf("%s\n", strings.Join(a, "\n"))
|
||||
}
|
||||
|
||||
// SignedHeaders
|
||||
func SignedHeaders(r *http.Request) string {
|
||||
var a []string
|
||||
for key := range r.Header {
|
||||
a = append(a, strings.ToLower(key))
|
||||
}
|
||||
a = append(a, HeaderHost)
|
||||
sort.Strings(a)
|
||||
return fmt.Sprintf("%s", strings.Join(a, ";"))
|
||||
}
|
||||
|
||||
// RequestPayload
|
||||
func RequestPayload(r *http.Request) ([]byte, error) {
|
||||
if r.Body == nil {
|
||||
return []byte(""), nil
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
||||
return b, err
|
||||
}
|
||||
|
||||
// Return the Credential Scope. See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
func CredentialScope(t time.Time, regionName, serviceName string) string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", t.UTC().Format(BasicDateFormatShort), regionName, serviceName, TerminationString)
|
||||
}
|
||||
|
||||
// Create a "String to Sign". See http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
func StringToSign(canonicalRequest, credentialScope string, t time.Time) string {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(canonicalRequest))
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%x",
|
||||
Algorithm, t.UTC().Format(BasicDateFormat), credentialScope, hash.Sum(nil))
|
||||
}
|
||||
|
||||
// Generate a "signing key" to sign the "String To Sign". See http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||
func GenerateSigningKey(secretKey, regionName, serviceName string, t time.Time) ([]byte, error) {
|
||||
|
||||
key := []byte(PreSKString + secretKey)
|
||||
var err error
|
||||
dateStamp := t.UTC().Format(BasicDateFormatShort)
|
||||
data := []string{dateStamp, regionName, serviceName, TerminationString}
|
||||
for _, d := range data {
|
||||
key, err = hmacsha256(key, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// Create the HWS Signature. See http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||
func SignStringToSign(stringToSign string, signingKey []byte) (string, error) {
|
||||
hm, err := hmacsha256(signingKey, stringToSign)
|
||||
return fmt.Sprintf("%x", hm), err
|
||||
}
|
||||
|
||||
// HexEncodeSHA256Hash returns hexcode of sha256
|
||||
func HexEncodeSHA256Hash(body []byte) (string, error) {
|
||||
hash := sha256.New()
|
||||
if body == nil {
|
||||
body = []byte("")
|
||||
}
|
||||
_, err := hash.Write(body)
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), err
|
||||
}
|
||||
|
||||
// Get the finalized value for the "Authorization" header. The signature parameter is the output from SignStringToSign
|
||||
func AuthHeaderValue(signature, accessKey, credentialScope, signedHeaders string) string {
|
||||
return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", Algorithm, accessKey, credentialScope, signedHeaders, signature)
|
||||
}
|
||||
|
||||
func trimString(s string) string {
|
||||
var trimedString []byte
|
||||
inQuote := false
|
||||
var lastChar byte
|
||||
s = strings.TrimSpace(s)
|
||||
for _, v := range []byte(s) {
|
||||
if byte(v) == byte('"') {
|
||||
inQuote = !inQuote
|
||||
}
|
||||
if lastChar == byte(' ') && byte(v) == byte(' ') && !inQuote {
|
||||
continue
|
||||
}
|
||||
trimedString = append(trimedString, v)
|
||||
lastChar = v
|
||||
}
|
||||
return string(trimedString)
|
||||
}
|
||||
|
||||
type Signer interface {
|
||||
Sign(*http.Request) error
|
||||
}
|
||||
|
||||
// Signature HWS meta
|
||||
type SignerHws struct {
|
||||
AppKey string
|
||||
AppSecret string
|
||||
Region string
|
||||
Service string
|
||||
}
|
||||
|
||||
// SignRequest set Authorization header
|
||||
func (s *SignerHws) Sign(r *http.Request) error {
|
||||
var t time.Time
|
||||
var err error
|
||||
var dt string
|
||||
if dt = r.Header.Get(HeaderXDate); dt != "" {
|
||||
t, err = time.Parse(BasicDateFormat, dt)
|
||||
} else if dt = r.Header.Get(HeaderDate); dt != "" {
|
||||
t, err = time.Parse(time.RFC1123, dt)
|
||||
}
|
||||
if err != nil || dt == "" {
|
||||
r.Header.Del(HeaderDate)
|
||||
t = time.Now()
|
||||
r.Header.Set(HeaderXDate, t.UTC().Format(BasicDateFormat))
|
||||
}
|
||||
canonicalRequest, err := CanonicalRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Region := DefaultRegion
|
||||
Service := DefaultService
|
||||
if s.Region != "" {
|
||||
Region = s.Region
|
||||
}
|
||||
if s.Service != "" {
|
||||
Service = s.Service
|
||||
}
|
||||
credentialScope := CredentialScope(t, Region, Service)
|
||||
stringToSign := StringToSign(canonicalRequest, credentialScope, t)
|
||||
key, err := GenerateSigningKey(s.AppSecret, Region, Service, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature, err := SignStringToSign(stringToSign, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedHeaders := SignedHeaders(r)
|
||||
authValue := AuthHeaderValue(signature, s.AppKey, credentialScope, signedHeaders)
|
||||
r.Header.Set(HeaderAuthorization, authValue)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user