Skip to content

Commit 2b4d0dc

Browse files
tkentTerence KentEngHabu
authored
Azure AD authentication support (and SDK upgrade) (#9)
* A refactor of the `azure` module to support authenticating via either Azure AD or shared keys. This required a larger-than-expected series of changes because the Azure SDK now very different than the one stow used before. This is an early commit to get changes out there for discussion. Included here is: - An upgrade of the Azure SDK used for the project (needed to support AD auth). This, unfortunately also brought in a whole series if indirect upgrades so testing will be needed. - Support for authenticating via either Azure AD or Shared keys - Removal of several azure config values (ConfigAPIVersion, ConfigUseHTTPS) and addition of ConfigUploadConcurrency. - Removal of the multi-part upload code for Azure, this is now included in the SDK - Addition of new presigned url tests into the general stow test/ module. - Addition of a new terraform module to stand up an azure storage account for testing. Signed-off-by: Terence Kent <terence.kent@mcg.com> * fix misspellings (doh) Signed-off-by: Terence Kent <terence.kent@mcg.com> * Update azure/config.go Accepting len(var)==0 vs var=="" Co-authored-by: Haytham Abuelfutuh <haytham@afutuh.com> Signed-off-by: Terence Kent <terence.kent@mcg.com> * Update azure/config.go Accepting improved error message Co-authored-by: Haytham Abuelfutuh <haytham@afutuh.com> Signed-off-by: Terence Kent <terence.kent@mcg.com> * Changes before moving PR from draft. No impact to tests or functionality at this point: - Removed all inline context.Background() calls when using Azure SDK methods. Now set an explicit `ctx` variable at the top of each function. - Added comments around the clock skew code, and moved all clock skews to 15 minutes (the azure-recommended value). Also left a comment explaining the decision. - Made the use of removed config values an error state for validation. Added a test to ensure this happens. - Moved some errant logging in the `checkMetadata` test method to only show up in inequality, to keep test output clean. Signed-off-by: Terence Kent <terence.kent@mcg.com> * Missed that the previous version of the Azure implementation silently ignored failures due to creating an already-existing container! The code from the old SDK to ignore "already exists" was preserved, but not working with the new SDK. - Updated the check to work with the new SDK. - Added a general test to confirm this behavior. Note: It's debatable if this should be the default behavior for all stow implementations. However, it seems like whatever the desired behavior is, it should be consistent across all implementations. Signed-off-by: Terence Kent <terence.kent@mcg.com> --------- Signed-off-by: Terence Kent <terence.kent@mcg.com> Co-authored-by: Terence Kent <terence.kent@mcg.com> Co-authored-by: Haytham Abuelfutuh <haytham@afutuh.com>
1 parent c7a3695 commit 2b4d0dc

22 files changed

+850
-590
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ tests.xml
3030
vendor/
3131

3232
.vscode/
33+
.terragrunt-cache

azure/config.go

Lines changed: 94 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,74 +2,82 @@ package azure
22

33
import (
44
"errors"
5+
"fmt"
6+
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
7+
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
58
"net/url"
69
"strconv"
710

8-
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
9-
10-
az "github.com/Azure/azure-sdk-for-go/storage"
1111
"github.com/flyteorg/stow"
1212
)
1313

1414
// ConfigAccount should be the name of your storage account in the Azure portal
1515
// ConfigKey should be an access key
16-
// ConfigBaseUrl is the base URL of the cloud you want to connect to. The default
17-
// is Azure Public cloud
18-
// ConfigAPIVersion is the Azure Storage API version string used when a
19-
// client is created.
20-
// ConfigUseHTTPS specifies whether you want to use HTTPS to connect
16+
// ConfigDomainSuffix the domain suffix to use for storage account communication. The default is the Azure Public cloud
17+
// ConfigUploadConcurrency the upload concurrency to use when uploading. Default is 4.
18+
// ConfigBaseUrlDepreciated Kept for backwards compatability, use ConfigDomainSuffix instead
2119
const (
22-
ConfigAccount = "account"
23-
ConfigKey = "key"
24-
ConfigBaseUrl = "base_url"
25-
ConfigAPIVersion = "api_version"
26-
ConfigUseHTTPS = "use_https"
20+
ConfigAccount = "account"
21+
ConfigKey = "key"
22+
ConfigDomainSuffix = "domain_suffix"
23+
ConfigUploadConcurrency = "upload_concurrency"
24+
ConfigBaseUrlDepreciated = "base_url"
2725
)
2826

27+
// Removed configuration values, will cause failures if used.
28+
const (
29+
ConfigUseHttpsRemoved = "use_https"
30+
ConfigApiVersionRemoved = "api_version"
31+
)
32+
33+
var removedConfigKeys = []string{ConfigUseHttpsRemoved, ConfigApiVersionRemoved}
34+
2935
// Kind is the kind of Location this package provides.
3036
const Kind = "azure"
31-
const defaultBaseUrl = "core.windows.net"
32-
const defaultAPIVersion = "2018-03-28"
33-
const defaultHTTPSStr = "true"
37+
38+
// defaultDomainSuffix is the domain suffix for the Azure Public Cloud
39+
const defaultDomainSuffix = "core.windows.net"
40+
41+
// defaultUploadConcurrency is the default upload concurrency
42+
const defaultUploadConcurrency = 4
3443

3544
func init() {
3645
validatefn := func(config stow.Config) error {
3746
_, ok := config.Config(ConfigAccount)
3847
if !ok {
3948
return errors.New("missing account id")
4049
}
41-
_, ok = config.Config(ConfigKey)
42-
if !ok {
43-
return errors.New("missing auth key")
50+
for _, removedConfigKey := range removedConfigKeys {
51+
_, ok = config.Config(removedConfigKey)
52+
if ok {
53+
return fmt.Errorf("removed config option used [%s]", removedConfigKey)
54+
}
4455
}
4556
return nil
4657
}
4758
makefn := func(config stow.Config) (stow.Location, error) {
48-
_, ok := config.Config(ConfigAccount)
59+
acctName, ok := config.Config(ConfigAccount)
4960
if !ok {
5061
return nil, errors.New("missing account id")
5162
}
52-
_, ok = config.Config(ConfigKey)
53-
if !ok {
54-
return nil, errors.New("missing auth key")
55-
}
56-
l := &location{
57-
config: config,
58-
}
5963

60-
acc, key, env, api, https, err := getAccount(l.config)
61-
if err != nil {
62-
return nil, err
64+
var uploadConcurrency int
65+
var err error
66+
uploadConcurrencyStr, ok := config.Config(ConfigUploadConcurrency)
67+
if !ok || len(uploadConcurrencyStr) == 0 {
68+
uploadConcurrency = defaultUploadConcurrency
69+
} else {
70+
uploadConcurrency, err = strconv.Atoi(uploadConcurrencyStr)
71+
if err != nil {
72+
return nil, fmt.Errorf("invalid upload concurrency [%v]", uploadConcurrency)
73+
}
6374
}
64-
65-
l.account = acc
66-
67-
l.client, err = newBlobStorageClient(acc, key, env, api, https)
68-
if err != nil {
69-
return nil, err
75+
l := &location{
76+
accountName: acctName,
77+
uploadConcurrency: uploadConcurrency,
7078
}
7179

72-
l.sharedCreds, err = azblob.NewSharedKeyCredential(acc, key)
80+
l.client, l.preSigner, err = makeAccountClient(config)
7381
if err != nil {
7482
return nil, err
7583
}
@@ -88,50 +96,69 @@ func init() {
8896
stow.Register(Kind, makefn, kindfn, validatefn)
8997
}
9098

91-
func getAccount(cfg stow.Config) (account, key string, baseUrl string, APIVersion string, useHTTPS bool, err error) {
92-
acc, ok := cfg.Config(ConfigAccount)
99+
// makeAccountClient is a factory function for producing client instances
100+
func makeAccountClient(cfg stow.Config) (*azblob.Client, RequestPreSigner, error) {
101+
accountName, ok := cfg.Config(ConfigAccount)
93102
if !ok {
94-
return "", "", "", "", false, errors.New("missing account id")
103+
return nil, nil, errors.New("missing account id")
95104
}
96105

97-
key, ok = cfg.Config(ConfigKey)
98-
if !ok {
99-
return "", "", "", "", false, errors.New("missing auth key")
100-
}
101-
102-
baseUrl = getBaseAzureUrlOrDefault(cfg)
106+
domainSuffix := resolveAzureDomainSuffix(cfg)
107+
serviceUrl := fmt.Sprintf("https://%s.blob.%s", accountName, domainSuffix)
103108

104-
APIVersion, ok = cfg.Config(ConfigAPIVersion)
105-
if !ok {
106-
APIVersion = defaultAPIVersion
109+
key, ok := cfg.Config(ConfigKey)
110+
if ok && key != "" {
111+
return newSharedKeyClient(accountName, key, serviceUrl)
107112
}
113+
return newDefaultAzureIdentityClient(serviceUrl)
114+
}
108115

109-
var useHTTPSStr string
110-
useHTTPSStr, ok = cfg.Config(ConfigUseHTTPS)
111-
if !ok {
112-
useHTTPSStr = defaultHTTPSStr
116+
// newSharedKeyClient creates client objects for working with a storage account
117+
// using shared keys.
118+
func newSharedKeyClient(accountName, key, serviceUrl string) (*azblob.Client, RequestPreSigner, error) {
119+
sharedKeyCred, err := azblob.NewSharedKeyCredential(accountName, key)
120+
if err != nil {
121+
return nil, nil, err
113122
}
114-
useHTTPS, err = strconv.ParseBool(useHTTPSStr)
123+
client, err := azblob.NewClientWithSharedKeyCredential(
124+
serviceUrl,
125+
sharedKeyCred,
126+
nil)
115127
if err != nil {
116-
return "", "", "", "", false, errors.New("invalid value for use_https_str")
128+
return nil, nil, err
117129
}
118-
return acc, key, baseUrl, APIVersion, useHTTPS, nil
130+
preSigner, err := NewSharedKeyRequestPreSigner(accountName, key)
131+
if err != nil {
132+
return nil, nil, err
133+
}
134+
return client, preSigner, nil
119135
}
120136

121-
func getBaseAzureUrlOrDefault(cfg stow.Config) string {
122-
baseUrl, ok := cfg.Config(ConfigBaseUrl)
123-
if !ok || baseUrl == "" {
124-
baseUrl = defaultBaseUrl
137+
// newDefaultAzureIdentityClient creates client objects for working with a storage
138+
// account using Azure AD auth, resolved using the default Azure credential chain.
139+
func newDefaultAzureIdentityClient(serviceUrl string) (*azblob.Client, RequestPreSigner, error) {
140+
cred, err := azidentity.NewDefaultAzureCredential(nil)
141+
if err != nil {
142+
return nil, nil, err
125143
}
126-
return baseUrl
144+
client, err := azblob.NewClient(serviceUrl, cred, nil)
145+
if err != nil {
146+
return nil, nil, err
147+
}
148+
preSigner, err := NewDelegatedKeyPreSigner(client.ServiceClient())
149+
return client, preSigner, nil
127150
}
128151

129-
func newBlobStorageClient(account, key string, baseUrl string, APIVersion string, useHTTPS bool) (*az.BlobStorageClient, error) {
130-
basicClient, err := az.NewClient(account, key, baseUrl, APIVersion, useHTTPS)
131-
if err != nil {
132-
return nil, errors.New("bad credentials")
152+
// resolveAzureDomainSuffix returns the Azure domain suffix to use
153+
func resolveAzureDomainSuffix(cfg stow.Config) string {
154+
domainSuffix, ok := cfg.Config(ConfigDomainSuffix)
155+
if ok && domainSuffix != "" {
156+
return domainSuffix
133157
}
134158

135-
client := basicClient.GetBlobService()
136-
return &client, err
159+
domainSuffix, ok = cfg.Config(ConfigBaseUrlDepreciated)
160+
if ok && domainSuffix != "" {
161+
return domainSuffix
162+
}
163+
return defaultDomainSuffix
137164
}

0 commit comments

Comments
 (0)