File: helpers.go

package info (click to toggle)
golang-github-tombuildsstuff-giovanni 0.20.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 15,908 kB
  • sloc: makefile: 3
file content (122 lines) | stat: -rw-r--r-- 3,600 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package auth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

// NOTE: this all came from the Azure SDK for Go, but has been refactored to aid creating a Preparer.

// buildCanonicalizedHeader builds the Canonicalized Header required to sign Storage Requests
func buildCanonicalizedHeader(headers http.Header) string {
	cm := make(map[string]string)

	for k, v := range headers {
		headerName := strings.TrimSpace(strings.ToLower(k))
		if strings.HasPrefix(headerName, "x-ms-") {
			cm[headerName] = v[0]
		}
	}

	if len(cm) == 0 {
		return ""
	}

	var keys []string
	for key := range cm {
		keys = append(keys, key)
	}

	sort.Strings(keys)

	ch := bytes.NewBufferString("")

	for _, key := range keys {
		ch.WriteString(key)
		ch.WriteRune(':')
		ch.WriteString(cm[key])
		ch.WriteRune('\n')
	}

	return strings.TrimSuffix(string(ch.Bytes()), "\n")
}

// buildCanonicalizedResource builds the Canonical Resource required for to sign Storage Account requests
func buildCanonicalizedResource(uri, accountName string) (*string, error) {
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}

	cr := bytes.NewBufferString("")
	if accountName != StorageEmulatorAccountName {
		cr.WriteString("/")
		cr.WriteString(primaryStorageAccountName(accountName))
	}

	if len(u.Path) > 0 {
		// Any portion of the CanonicalizedResource string that is derived from
		// the resource's URI should be encoded exactly as it is in the URI.
		// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
		cr.WriteString(u.EscapedPath())
	}

	// TODO: replace this with less of a hack
	if comp := u.Query().Get("comp"); comp != "" {
		cr.WriteString(fmt.Sprintf("?comp=%s", comp))
	}

	out := string(cr.Bytes())
	return &out, nil
}

func formatSharedKeyLiteAuthorizationHeader(accountName, key string) string {
	canonicalizedAccountName := primaryStorageAccountName(accountName)
	return fmt.Sprintf("SharedKeyLite %s:%s", canonicalizedAccountName, key)
}

// hmacValue base-64 decodes the storageAccountKey, then signs the string with it
// as outlined here: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
func hmacValue(storageAccountKey, canonicalizedString string) string {
	key, err := base64.StdEncoding.DecodeString(storageAccountKey)
	if err != nil {
		return ""
	}

	encr := hmac.New(sha256.New, []byte(key))
	encr.Write([]byte(canonicalizedString))
	return base64.StdEncoding.EncodeToString(encr.Sum(nil))
}

// prepareHeadersForRequest prepares a request so that it can be signed
// by ensuring the `date` and `x-ms-date` headers are set
func prepareHeadersForRequest(r *http.Request) {
	if r.Header == nil {
		r.Header = http.Header{}
	}

	date := time.Now().UTC().Format(http.TimeFormat)

	// a date must be set, X-Ms-Date should be used when both are set; but let's set both for completeness
	r.Header.Set("date", date)
	r.Header.Set("x-ms-date", date)
}

// primaryStorageAccountName returns the name of the primary for a given Storage Account
func primaryStorageAccountName(input string) string {
	// from https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
	// If you are accessing the secondary location in a storage account for which
	// read-access geo-replication (RA-GRS) is enabled, do not include the
	// -secondary designation in the authorization header.
	// For authorization purposes, the account name is always the name of the primary location,
	// even for secondary access.
	return strings.TrimSuffix(input, "-secondary")
}