Skip to content

Commit ef26cdd

Browse files
authored
Merge pull request #71 from ZeroMagic/keyvault
feat: add key vault support in static provisioning scenario
2 parents 2e766be + 1c6696f commit ef26cdd

File tree

8 files changed

+8364
-43
lines changed

8 files changed

+8364
-43
lines changed

Gopkg.lock

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/blobfuse/azure.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ package blobfuse
1919
import (
2020
"fmt"
2121
"os"
22+
"strings"
23+
24+
kv "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
25+
"github.com/Azure/go-autorest/autorest"
26+
"github.com/Azure/go-autorest/autorest/adal"
27+
"github.com/Azure/go-autorest/autorest/azure"
2228

2329
"k8s.io/klog"
24-
"k8s.io/legacy-cloud-providers/azure"
30+
azureprovider "k8s.io/legacy-cloud-providers/azure"
31+
32+
"golang.org/x/net/context"
2533
)
2634

2735
// GetCloudProvider get Azure Cloud Provider
28-
func GetCloudProvider() (*azure.Cloud, error) {
36+
func GetCloudProvider() (*azureprovider.Cloud, error) {
2937
credFile, ok := os.LookupEnv("AZURE_CREDENTIAL_FILE")
3038
if ok {
3139
klog.V(2).Infof("AZURE_CREDENTIAL_FILE env var set as %v", credFile)
@@ -41,14 +49,66 @@ func GetCloudProvider() (*azure.Cloud, error) {
4149
}
4250
defer f.Close()
4351

44-
cloud, err := azure.NewCloud(f)
52+
cloud, err := azureprovider.NewCloud(f)
4553
if err != nil {
4654
return nil, err
4755
}
4856

49-
az, ok := cloud.(*azure.Cloud)
57+
az, ok := cloud.(*azureprovider.Cloud)
5058
if !ok || az == nil {
5159
return nil, fmt.Errorf("failed to get Azure Cloud Provider. GetCloudProvider returned %v instead", cloud)
5260
}
5361
return az, nil
5462
}
63+
64+
// getKeyVaultSecretContent get content of the keyvault secret
65+
func (d *Driver) getKeyVaultSecretContent(ctx context.Context, vaultURL string, secretName string, secretVersion string) (content string, err error) {
66+
kvClient, err := d.initializeKvClient()
67+
if err != nil {
68+
return "", fmt.Errorf("failed to get keyvaultClient: %v", err)
69+
}
70+
71+
secret, err := kvClient.GetSecret(ctx, vaultURL, secretName, secretVersion)
72+
if err != nil {
73+
return "", fmt.Errorf("failed to use vaultURL(%v), sercretName(%v), secretVersion(%v) to get secret: %v", vaultURL, secretName, secretVersion, err)
74+
}
75+
return *secret.Value, nil
76+
}
77+
78+
func (d *Driver) initializeKvClient() (*kv.BaseClient, error) {
79+
kvClient := kv.New()
80+
token, err := d.getKeyvaultToken()
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
kvClient.Authorizer = token
86+
return &kvClient, nil
87+
}
88+
89+
// getKeyvaultToken retrieves a new service principal token to access keyvault
90+
func (d *Driver) getKeyvaultToken() (authorizer autorest.Authorizer, err error) {
91+
env := d.cloud.Environment
92+
93+
kvEndPoint := strings.TrimSuffix(env.KeyVaultEndpoint, "/")
94+
servicePrincipalToken, err := d.getServicePrincipalToken(env, kvEndPoint)
95+
if err != nil {
96+
return nil, err
97+
}
98+
authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
99+
return authorizer, nil
100+
}
101+
102+
// getServicePrincipalToken creates a new service principal token based on the configuration
103+
func (d *Driver) getServicePrincipalToken(env azure.Environment, resource string) (*adal.ServicePrincipalToken, error) {
104+
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, d.cloud.TenantID)
105+
if err != nil {
106+
return nil, fmt.Errorf("creating the OAuth config: %v", err)
107+
}
108+
109+
return adal.NewServicePrincipalToken(
110+
*oauthConfig,
111+
d.cloud.AADClientID,
112+
d.cloud.AADClientSecret,
113+
resource)
114+
}

pkg/blobfuse/blobfuse.go

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"k8s.io/kubernetes/pkg/util/mount"
2929
k8sutil "k8s.io/kubernetes/pkg/volume/util"
3030
"k8s.io/legacy-cloud-providers/azure"
31+
32+
"golang.org/x/net/context"
3133
)
3234

3335
const (
@@ -160,9 +162,9 @@ func appendDefaultMountOptions(mountOptions []string) []string {
160162

161163
// get storage account from secrets map
162164
// returns <accountName, accountKey, accountSasToken>
163-
func getStorageAccount(secrets map[string]string) (string, string, string, error) {
165+
func getStorageAccountFromSecretsMap(secrets map[string]string) (string, string, string, error) {
164166
if secrets == nil {
165-
return "", "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
167+
return "", "", "", fmt.Errorf("unexpected: getStorageAccountFromSecretsMap secrets is nil")
166168
}
167169

168170
var accountName, accountKey, accountSasToken string
@@ -228,3 +230,84 @@ func checkContainerNameBeginAndEnd(containerName string) bool {
228230

229231
return false
230232
}
233+
234+
// isSASToken checks if the key contains the patterns. Because a SAS Token must have these strings, use them to judge.
235+
func isSASToken(key string) bool {
236+
return strings.Contains(key, "?sv=")
237+
}
238+
239+
// getStorageAccountAndContainer: get storage account and container info
240+
// returns <accountName, accountKey, accountSasToken, containerName>
241+
func (d *Driver) getStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
242+
var (
243+
accountName string
244+
accountKey string
245+
accountSasToken string
246+
247+
containerName string
248+
249+
keyVaultURL string
250+
keyVaultSecretName string
251+
keyVaultSecretVersion string
252+
253+
err error
254+
)
255+
256+
for k, v := range attrib {
257+
switch strings.ToLower(k) {
258+
case "containername":
259+
containerName = v
260+
case "keyvaulturl":
261+
keyVaultURL = v
262+
case "keyvaultsecretname":
263+
keyVaultSecretName = v
264+
case "keyvaultsecretversion":
265+
keyVaultSecretVersion = v
266+
case "storageaccountname":
267+
accountName = v
268+
}
269+
}
270+
271+
// 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
272+
// 2. Then if secrets map is not nil, use the key stored in the secrets map.
273+
// 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
274+
if keyVaultURL != "" {
275+
key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
276+
if err != nil {
277+
return "", "", "", "", err
278+
}
279+
if isSASToken(key) {
280+
accountSasToken = key
281+
} else {
282+
accountKey = key
283+
}
284+
} else {
285+
if len(secrets) == 0 {
286+
var resourceGroupName string
287+
resourceGroupName, accountName, containerName, err = getContainerInfo(volumeID)
288+
if err != nil {
289+
return "", "", "", "", err
290+
}
291+
292+
if resourceGroupName == "" {
293+
resourceGroupName = d.cloud.ResourceGroup
294+
}
295+
296+
accountKey, err = d.cloud.GetStorageAccesskey(accountName, resourceGroupName)
297+
if err != nil {
298+
return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %v", accountName, resourceGroupName, err)
299+
}
300+
} else {
301+
accountName, accountKey, accountSasToken, err = getStorageAccountFromSecretsMap(secrets)
302+
if err != nil {
303+
return "", "", "", "", err
304+
}
305+
}
306+
}
307+
308+
if containerName == "" {
309+
return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
310+
}
311+
312+
return accountName, accountKey, accountSasToken, containerName, nil
313+
}

pkg/blobfuse/blobfuse_test.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func TestGetContainerInfo(t *testing.T) {
116116
}
117117
}
118118

119-
func TestGetStorageAccount(t *testing.T) {
119+
func TestGetStorageAccountFromSecretsMap(t *testing.T) {
120120
emptyAccountKeyMap := map[string]string{
121121
"accountname": "testaccount",
122122
"accountkey": "",
@@ -232,14 +232,14 @@ func TestGetStorageAccount(t *testing.T) {
232232
expected1: "",
233233
expected2: "",
234234
expected3: "",
235-
expected4: fmt.Errorf("unexpected: getStorageAccount secrets is nil"),
235+
expected4: fmt.Errorf("unexpected: getStorageAccountFromSecretsMap secrets is nil"),
236236
},
237237
}
238238

239239
for _, test := range tests {
240-
result1, result2, result3, result4 := getStorageAccount(test.options)
240+
result1, result2, result3, result4 := getStorageAccountFromSecretsMap(test.options)
241241
if !reflect.DeepEqual(result1, test.expected1) || !reflect.DeepEqual(result2, test.expected2) || !reflect.DeepEqual(result3, test.expected3) {
242-
t.Errorf("input: %q, getStorageAccount result1: %q, expected1: %q, result2: %q, expected2: %q, result3: %q, expected3: %q, result4: %q, expected4: %q", test.options, result1, test.expected1, result2, test.expected2,
242+
t.Errorf("input: %q, getStorageAccountFromSecretsMap result1: %q, expected1: %q, result2: %q, expected2: %q, result3: %q, expected3: %q, result4: %q, expected4: %q", test.options, result1, test.expected1, result2, test.expected2,
243243
result3, test.expected3, result4, test.expected4)
244244
} else {
245245
if result1 == "" || (result2 == "" && result3 == "") {
@@ -322,3 +322,30 @@ func TestCheckContainerNameBeginAndEnd(t *testing.T) {
322322
}
323323
}
324324
}
325+
326+
func TestIsSASToken(t *testing.T) {
327+
tests := []struct {
328+
key string
329+
expected bool
330+
}{
331+
{
332+
key: "?sv=2017-03-28&ss=bfqt&srt=sco&sp=rwdlacup",
333+
expected: true,
334+
},
335+
{
336+
key: "&ss=bfqt&srt=sco&sp=rwdlacup",
337+
expected: false,
338+
},
339+
{
340+
key: "123456789vbDWANIJ319Fqabcded3wwLRnxK70zRJ",
341+
expected: false,
342+
},
343+
}
344+
345+
for _, test := range tests {
346+
result := isSASToken(test.key)
347+
if !reflect.DeepEqual(result, test.expected) {
348+
t.Errorf("input: %q, isSASToken result: %v, expected: %v", test.key, result, test.expected)
349+
}
350+
}
351+
}

pkg/blobfuse/nodeserver.go

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -76,40 +76,11 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
7676
volumeID := req.GetVolumeId()
7777
attrib := req.GetVolumeContext()
7878
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
79-
80-
var accountName, accountKey, accountSasToken, containerName string
81-
8279
secrets := req.GetSecrets()
83-
if len(secrets) == 0 {
84-
var resourceGroupName string
85-
resourceGroupName, accountName, containerName, err = getContainerInfo(volumeID)
86-
if err != nil {
87-
return nil, err
88-
}
89-
90-
if resourceGroupName == "" {
91-
resourceGroupName = d.cloud.ResourceGroup
92-
}
93-
94-
accountKey, err = d.cloud.GetStorageAccesskey(accountName, resourceGroupName)
95-
if err != nil {
96-
return nil, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %v", accountName, resourceGroupName, err)
97-
}
98-
} else {
99-
for k, v := range attrib {
100-
switch strings.ToLower(k) {
101-
case "containername":
102-
containerName = v
103-
}
104-
}
105-
if containerName == "" {
106-
return nil, fmt.Errorf("could not find containerName from attributes(%v)", attrib)
107-
}
10880

109-
accountName, accountKey, accountSasToken, err = getStorageAccount(secrets)
110-
if err != nil {
111-
return nil, err
112-
}
81+
accountName, accountKey, accountSasToken, containerName, err := d.getStorageAccountAndContainer(ctx, volumeID, attrib, secrets)
82+
if err != nil {
83+
return nil, err
11384
}
11485

11586
options := []string{"--use-https=true"}

0 commit comments

Comments
 (0)