• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / blob-csi-driver / 16359912094

18 Jul 2025 01:28AM UTC coverage: 80.461%. Remained the same
16359912094

Pull #2086

github

andyzhangx
feat: install blobfuse 2.5.0 as default version
Pull Request #2086: feat: install blobfuse 2.5.0 as default version

2446 of 3040 relevant lines covered (80.46%)

8.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.99
/pkg/blob/blob.go
1
/*
2
Copyright 2019 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package blob
18

19
import (
20
        "context"
21
        "encoding/json"
22
        "errors"
23
        "flag"
24
        "fmt"
25
        "os"
26
        "path/filepath"
27
        "strconv"
28
        "strings"
29
        "sync"
30
        "time"
31

32
        "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
33
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
34
        "github.com/container-storage-interface/spec/lib/go/csi"
35
        grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
36
        "github.com/pborman/uuid"
37
        "google.golang.org/grpc"
38
        v1 "k8s.io/api/core/v1"
39
        apierror "k8s.io/apimachinery/pkg/api/errors"
40
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41
        "k8s.io/client-go/kubernetes"
42
        "k8s.io/klog/v2"
43
        mount "k8s.io/mount-utils"
44
        utilexec "k8s.io/utils/exec"
45

46
        csicommon "sigs.k8s.io/blob-csi-driver/pkg/csi-common"
47
        "sigs.k8s.io/blob-csi-driver/pkg/util"
48
        "sigs.k8s.io/cloud-provider-azure/pkg/azclient"
49
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
50
        "sigs.k8s.io/cloud-provider-azure/pkg/provider/storage"
51
)
52

53
const (
54
        // DefaultDriverName holds the name of the csi-driver
55
        DefaultDriverName              = "blob.csi.azure.com"
56
        blobCSIDriverName              = "blob_csi_driver"
57
        separator                      = "#"
58
        volumeIDTemplate               = "%s#%s#%s#%s#%s#%s"
59
        secretNameTemplate             = "azure-storage-account-%s-secret"
60
        serverNameField                = "server"
61
        storageEndpointSuffixField     = "storageendpointsuffix"
62
        tagsField                      = "tags"
63
        matchTagsField                 = "matchtags"
64
        protocolField                  = "protocol"
65
        accountNameField               = "accountname"
66
        accountKeyField                = "accountkey"
67
        storageAccountField            = "storageaccount"
68
        storageAccountTypeField        = "storageaccounttype"
69
        skuNameField                   = "skuname"
70
        subscriptionIDField            = "subscriptionid"
71
        resourceGroupField             = "resourcegroup"
72
        locationField                  = "location"
73
        secretNameField                = "secretname"
74
        secretNamespaceField           = "secretnamespace"
75
        containerNameField             = "containername"
76
        containerNamePrefixField       = "containernameprefix"
77
        storeAccountKeyField           = "storeaccountkey"
78
        getLatestAccountKeyField       = "getlatestaccountkey"
79
        isHnsEnabledField              = "ishnsenabled"
80
        softDeleteBlobsField           = "softdeleteblobs"
81
        softDeleteContainersField      = "softdeletecontainers"
82
        enableBlobVersioningField      = "enableblobversioning"
83
        getAccountKeyFromSecretField   = "getaccountkeyfromsecret"
84
        storageSPNClientIDField        = "azurestoragespnclientid"
85
        storageSPNTenantIDField        = "azurestoragespntenantid"
86
        storageAuthTypeField           = "azurestorageauthtype"
87
        storageAuthTypeMSI             = "msi"
88
        storageIdentityClientIDField   = "azurestorageidentityclientid"
89
        storageIdentityObjectIDField   = "azurestorageidentityobjectid"
90
        storageIdentityResourceIDField = "azurestorageidentityresourceid"
91
        msiEndpointField               = "msiendpoint"
92
        storageAADEndpointField        = "azurestorageaadendpoint"
93
        keyVaultURLField               = "keyvaulturl"
94
        keyVaultSecretNameField        = "keyvaultsecretname"
95
        keyVaultSecretVersionField     = "keyvaultsecretversion"
96
        storageAccountNameField        = "storageaccountname"
97
        allowBlobPublicAccessField     = "allowblobpublicaccess"
98
        allowSharedKeyAccessField      = "allowsharedkeyaccess"
99
        publicNetworkAccessField       = "publicnetworkaccess"
100
        requireInfraEncryptionField    = "requireinfraencryption"
101
        ephemeralField                 = "csi.storage.k8s.io/ephemeral"
102
        podNamespaceField              = "csi.storage.k8s.io/pod.namespace"
103
        serviceAccountTokenField       = "csi.storage.k8s.io/serviceAccount.tokens"
104
        clientIDField                  = "clientid"
105
        mountWithWITokenField          = "mountwithworkloadidentitytoken"
106
        tenantIDField                  = "tenantid"
107
        mountOptionsField              = "mountoptions"
108
        falseValue                     = "false"
109
        trueValue                      = "true"
110
        defaultSecretAccountName       = "azurestorageaccountname"
111
        defaultSecretAccountKey        = "azurestorageaccountkey"
112
        accountSasTokenField           = "azurestorageaccountsastoken"
113
        msiSecretField                 = "msisecret"
114
        storageSPNClientSecretField    = "azurestoragespnclientsecret"
115
        Fuse                           = "fuse"
116
        Fuse2                          = "fuse2"
117
        NFS                            = "nfs"
118
        AZNFS                          = "aznfs"
119
        NFSv3                          = "nfsv3"
120
        vnetResourceGroupField         = "vnetresourcegroup"
121
        vnetNameField                  = "vnetname"
122
        vnetLinkNameField              = "vnetlinkname"
123
        subnetNameField                = "subnetname"
124
        accessTierField                = "accesstier"
125
        networkEndpointTypeField       = "networkendpointtype"
126
        mountPermissionsField          = "mountpermissions"
127
        fsGroupChangePolicyField       = "fsgroupchangepolicy"
128
        useDataPlaneAPIField           = "usedataplaneapi"
129

130
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
131
        containerNameMinLength = 3
132
        containerNameMaxLength = 63
133

134
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
135
        tooManyRequests                         = "TooManyRequests"
136
        clientThrottled                         = "client throttled"
137
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
138
        containerBeingDeletedManagementAPIError = "container is being deleted"
139
        statusCodeNotFound                      = "StatusCode=404"
140
        httpCodeNotFound                        = "HTTPStatusCode: 404"
141

142
        // containerMaxSize is the max size of the blob container. See https://docs.microsoft.com/en-us/azure/storage/blobs/scalability-targets#scale-targets-for-blob-storage
143
        containerMaxSize = 100 * util.TiB
144

145
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
146

147
        defaultNamespace = "default"
148

149
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
150
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
151
        pvNameKey            = "csi.storage.k8s.io/pv/name"
152
        pvcNameMetadata      = "${pvc.metadata.name}"
153
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
154
        pvNameMetadata       = "${pv.metadata.name}"
155

156
        VolumeID = "volumeid"
157

158
        defaultStorageEndPointSuffix = "core.windows.net"
159

160
        FSGroupChangeNone = "None"
161
        // define tag value delimiter and default is comma
162
        tagValueDelimiterField = "tagvaluedelimiter"
163

164
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
165
)
166

167
var (
168
        supportedProtocolList            = []string{Fuse, Fuse2, NFS, AZNFS}
169
        retriableErrors                  = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
170
        supportedFSGroupChangePolicyList = []string{FSGroupChangeNone, string(v1.FSGroupChangeAlways), string(v1.FSGroupChangeOnRootMismatch)}
171

172
        // azcopyCloneVolumeOptions used in volume cloning between different storage account and --check-length to false because volume data may be in changing state, copy volume is not same as current source volume,
173
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
174
        azcopyCloneVolumeOptions  = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
175
        defaultAzureOAuthTokenDir = "/var/lib/kubelet/plugins/" + DefaultDriverName
176
)
177

178
// DriverOptions defines driver parameters specified in driver deployment
179
type DriverOptions struct {
180
        NodeID                                 string
181
        DriverName                             string
182
        BlobfuseProxyEndpoint                  string
183
        EnableBlobfuseProxy                    bool
184
        BlobfuseProxyConnTimout                int
185
        EnableBlobMockMount                    bool
186
        AllowInlineVolumeKeyAccessWithIdentity bool
187
        EnableGetVolumeStats                   bool
188
        AppendTimeStampInCacheDir              bool
189
        AppendMountErrorHelpLink               bool
190
        MountPermissions                       uint64
191
        EnableAznfsMount                       bool
192
        VolStatsCacheExpireInMinutes           int
193
        SasTokenExpirationMinutes              int
194
        WaitForAzCopyTimeoutMinutes            int
195
        EnableVolumeMountGroup                 bool
196
        FSGroupChangePolicy                    string
197
}
198

199
func (option *DriverOptions) AddFlags() {
1✔
200
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
201
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
202
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
203
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
204
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
205
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
206
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
207
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
208
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
209
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
210
        flag.BoolVar(&option.AppendMountErrorHelpLink, "append-mount-error-help-link", true, "Whether to include a link for help with mount errors when a mount error occurs.")
1✔
211
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
212
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
213
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
214
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
215
        flag.BoolVar(&option.EnableVolumeMountGroup, "enable-volume-mount-group", true, "indicates whether enabling VOLUME_MOUNT_GROUP")
1✔
216
        flag.StringVar(&option.FSGroupChangePolicy, "fsgroup-change-policy", "", "indicates how the volume's ownership will be changed by the driver, OnRootMismatch is the default value")
1✔
217
}
1✔
218

219
// Driver implements all interfaces of CSI drivers
220
type Driver struct {
221
        csicommon.CSIDriver
222
        // Embed UnimplementedXXXServer to ensure the driver returns Unimplemented for any
223
        // new RPC methods that might be introduced in future versions of the spec.
224
        csi.UnimplementedControllerServer
225
        csi.UnimplementedIdentityServer
226
        csi.UnimplementedNodeServer
227

228
        cloud                 *storage.AccountRepo
229
        clientFactory         azclient.ClientFactory
230
        networkClientFactory  azclient.ClientFactory
231
        KubeClient            kubernetes.Interface
232
        blobfuseProxyEndpoint string
233
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
234
        enableBlobMockMount                    bool
235
        enableBlobfuseProxy                    bool
236
        enableGetVolumeStats                   bool
237
        allowInlineVolumeKeyAccessWithIdentity bool
238
        appendTimeStampInCacheDir              bool
239
        appendMountErrorHelpLink               bool
240
        blobfuseProxyConnTimout                int
241
        mountPermissions                       uint64
242
        enableAznfsMount                       bool
243
        enableVolumeMountGroup                 bool
244
        fsGroupChangePolicy                    string
245
        mounter                                *mount.SafeFormatAndMount
246
        volLockMap                             *util.LockMap
247
        // A map storing all volumes with ongoing operations so that additional operations
248
        // for that same volume (as defined by VolumeID) return an Aborted error
249
        volumeLocks *volumeLocks
250
        // only for nfs feature
251
        subnetLockMap *util.LockMap
252
        // a map storing all volumes created by this driver <volumeName, accountName>
253
        volMap sync.Map
254
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
255
        dataPlaneAPIVolCache azcache.Resource
256
        // a timed cache storing account search history (solve account list throttling issue)
257
        accountSearchCache azcache.Resource
258
        // a timed cache storing volume stats <volumeID, volumeStats>
259
        volStatsCache azcache.Resource
260
        // a timed cache storing account which should use sastoken for azcopy based volume cloning
261
        azcopySasTokenCache azcache.Resource
262
        // a timed cache storing subnet operations
263
        subnetCache azcache.Resource
264
        // sas expiry time for azcopy in volume clone
265
        sasTokenExpirationMinutes int
266
        // timeout in minutes for waiting for azcopy to finish
267
        waitForAzCopyTimeoutMinutes int
268
        // azcopy for provide exec mock for ut
269
        azcopy *util.Azcopy
270
}
271

272
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
273
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
274
func NewDriver(options *DriverOptions, kubeClient kubernetes.Interface, cloud *storage.AccountRepo) *Driver {
142✔
275
        d := Driver{
142✔
276
                volLockMap:                             util.NewLockMap(),
142✔
277
                subnetLockMap:                          util.NewLockMap(),
142✔
278
                volumeLocks:                            newVolumeLocks(),
142✔
279
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
142✔
280
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
142✔
281
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
142✔
282
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
142✔
283
                enableBlobMockMount:                    options.EnableBlobMockMount,
142✔
284
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
142✔
285
                enableVolumeMountGroup:                 options.EnableVolumeMountGroup,
142✔
286
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
142✔
287
                mountPermissions:                       options.MountPermissions,
142✔
288
                enableAznfsMount:                       options.EnableAznfsMount,
142✔
289
                sasTokenExpirationMinutes:              options.SasTokenExpirationMinutes,
142✔
290
                waitForAzCopyTimeoutMinutes:            options.WaitForAzCopyTimeoutMinutes,
142✔
291
                fsGroupChangePolicy:                    options.FSGroupChangePolicy,
142✔
292
                azcopy:                                 &util.Azcopy{ExecCmd: &util.ExecCommand{}},
142✔
293
                KubeClient:                             kubeClient,
142✔
294
                cloud:                                  cloud,
142✔
295
        }
142✔
296
        d.Name = options.DriverName
142✔
297
        d.Version = driverVersion
142✔
298
        d.NodeID = options.NodeID
142✔
299
        if d.cloud != nil {
284✔
300
                d.clientFactory = d.cloud.ComputeClientFactory
142✔
301
                d.networkClientFactory = d.cloud.NetworkClientFactory
142✔
302
                if d.networkClientFactory == nil {
284✔
303
                        d.networkClientFactory = d.cloud.ComputeClientFactory
142✔
304
                }
142✔
305
        }
306

307
        var err error
142✔
308
        getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
165✔
309
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
142✔
310
                klog.Fatalf("%v", err)
×
311
        }
×
312
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
142✔
313
                klog.Fatalf("%v", err)
×
314
        }
×
315
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
142✔
316
                klog.Fatalf("%v", err)
×
317
        }
×
318

319
        if options.VolStatsCacheExpireInMinutes <= 0 {
284✔
320
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
142✔
321
        }
142✔
322
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
142✔
323
                klog.Fatalf("%v", err)
×
324
        }
×
325
        if d.subnetCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
142✔
326
                klog.Fatalf("%v", err)
×
327
        }
×
328

329
        d.mounter = &mount.SafeFormatAndMount{
142✔
330
                Interface: mount.New(""),
142✔
331
                Exec:      utilexec.New(),
142✔
332
        }
142✔
333

142✔
334
        // Initialize default library driver
142✔
335
        d.AddControllerServiceCapabilities(
142✔
336
                []csi.ControllerServiceCapability_RPC_Type{
142✔
337
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
142✔
338
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
142✔
339
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
142✔
340
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
142✔
341
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
142✔
342
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
142✔
343
                })
142✔
344
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
142✔
345
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
142✔
346
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
142✔
347
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
142✔
348
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
142✔
349
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
142✔
350
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
142✔
351
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
142✔
352
        })
142✔
353

142✔
354
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
142✔
355
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
142✔
356
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
142✔
357
        }
142✔
358
        if d.enableGetVolumeStats {
142✔
359
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
360
        }
×
361
        if d.enableVolumeMountGroup {
142✔
362
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
363
        }
×
364
        d.AddNodeServiceCapabilities(nodeCap)
142✔
365

142✔
366
        return &d
142✔
367
}
368

369
// Run driver initialization
370
func (d *Driver) Run(ctx context.Context, endpoint string) error {
2✔
371
        versionMeta, err := GetVersionYAML(d.Name)
2✔
372
        if err != nil {
2✔
373
                klog.Fatalf("%v", err)
×
374
        }
×
375
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
376
        opts := []grpc.ServerOption{
2✔
377
                grpc.ChainUnaryInterceptor(
2✔
378
                        grpcprom.NewServerMetrics().UnaryServerInterceptor(),
2✔
379
                        csicommon.LogGRPC,
2✔
380
                ),
2✔
381
        }
2✔
382
        s := grpc.NewServer(opts...)
2✔
383
        csi.RegisterIdentityServer(s, d)
2✔
384
        csi.RegisterControllerServer(s, d)
2✔
385
        csi.RegisterNodeServer(s, d)
2✔
386

2✔
387
        go func() {
4✔
388
                //graceful shutdown
2✔
389
                <-ctx.Done()
2✔
390
                s.GracefulStop()
2✔
391
        }()
2✔
392
        // Driver d act as IdentityServer, ControllerServer and NodeServer
393
        listener, err := csicommon.Listen(ctx, endpoint)
2✔
394
        if err != nil {
2✔
395
                klog.Fatalf("failed to listen to endpoint, error: %v", err)
×
396
        }
×
397
        err = s.Serve(listener)
2✔
398
        if errors.Is(err, grpc.ErrServerStopped) {
2✔
399
                klog.Infof("gRPC server stopped serving")
×
400
                return nil
×
401
        }
×
402
        return err
2✔
403
}
404

405
// GetContainerInfo get container info according to volume id
406
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
407
//
408
// e.g.
409
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
410
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
411
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
412
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
413
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
414
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
415
func GetContainerInfo(id string) (string, string, string, string, string, error) {
45✔
416
        segments := strings.Split(id, separator)
45✔
417
        if len(segments) < 3 {
57✔
418
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
12✔
419
        }
12✔
420
        var secretNamespace, subsID string
33✔
421
        if len(segments) > 4 {
39✔
422
                secretNamespace = segments[4]
6✔
423
        }
6✔
424
        if len(segments) > 5 {
36✔
425
                subsID = segments[5]
3✔
426
        }
3✔
427
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
33✔
428
}
429

430
// A container name must be a valid DNS name, conforming to the following naming rules:
431
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
432
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
433
//  3. All letters in a container name must be lowercase.
434
//  4. Container names must be from 3 through 63 characters long.
435
//
436
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
437
func getValidContainerName(volumeName, protocol string) string {
6✔
438
        containerName := strings.ToLower(volumeName)
6✔
439
        if len(containerName) > containerNameMaxLength {
7✔
440
                containerName = containerName[0:containerNameMaxLength]
1✔
441
        }
1✔
442
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
6✔
443
                // now we set as 63 for maximum container name length
×
444
                // todo: get cluster name
×
445
                containerName = generateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
446
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
447
        }
×
448
        return strings.Replace(containerName, "--", "-", -1)
6✔
449
}
450

451
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
452
        length := len(containerName)
12✔
453
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
454
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
455
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
456
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
457
                return true
10✔
458
        }
10✔
459

460
        return false
2✔
461
}
462

463
// isSASToken checks if the key contains the patterns.
464
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
465
func isSASToken(key string) bool {
3✔
466
        return strings.HasPrefix(key, "?")
3✔
467
}
3✔
468

469
// GetAuthEnv return <accountName, containerName, authEnv, error>
470
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
14✔
471
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
14✔
472
        if err != nil {
18✔
473
                // ignore volumeID parsing error
4✔
474
                klog.V(6).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
4✔
475
                err = nil
4✔
476
        }
4✔
477

478
        var (
14✔
479
                subsID                  string
14✔
480
                accountKey              string
14✔
481
                accountSasToken         string
14✔
482
                msiSecret               string
14✔
483
                storageSPNClientSecret  string
14✔
484
                storageSPNClientID      string
14✔
485
                storageSPNTenantID      string
14✔
486
                secretName              string
14✔
487
                pvcNamespace            string
14✔
488
                keyVaultURL             string
14✔
489
                keyVaultSecretName      string
14✔
490
                keyVaultSecretVersion   string
14✔
491
                azureStorageAuthType    string
14✔
492
                authEnv                 []string
14✔
493
                getAccountKeyFromSecret bool
14✔
494
                getLatestAccountKey     bool
14✔
495
                clientID                string
14✔
496
                mountWithWIToken        bool
14✔
497
                tenantID                string
14✔
498
                serviceAccountToken     string
14✔
499
        )
14✔
500

14✔
501
        for k, v := range attrib {
79✔
502
                switch strings.ToLower(k) {
65✔
503
                case subscriptionIDField:
3✔
504
                        subsID = v
3✔
505
                case resourceGroupField:
2✔
506
                        rgName = v
2✔
507
                case containerNameField:
6✔
508
                        containerName = v
6✔
509
                case keyVaultURLField:
1✔
510
                        keyVaultURL = v
1✔
511
                case keyVaultSecretNameField:
1✔
512
                        keyVaultSecretName = v
1✔
513
                case keyVaultSecretVersionField:
1✔
514
                        keyVaultSecretVersion = v
1✔
515
                case storageAccountField:
5✔
516
                        accountName = v
5✔
517
                case storageAccountNameField: // for compatibility
4✔
518
                        accountName = v
4✔
519
                case secretNameField:
3✔
520
                        secretName = v
3✔
521
                case secretNamespaceField:
4✔
522
                        secretNamespace = v
4✔
523
                case pvcNamespaceKey:
3✔
524
                        pvcNamespace = v
3✔
525
                case getAccountKeyFromSecretField:
4✔
526
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
4✔
527
                case storageAuthTypeField:
2✔
528
                        azureStorageAuthType = v
2✔
529
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
2✔
530
                case storageIdentityClientIDField:
1✔
531
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
532
                case storageIdentityObjectIDField:
1✔
533
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
534
                case storageIdentityResourceIDField:
1✔
535
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
536
                case msiEndpointField:
3✔
537
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
3✔
538
                case storageSPNClientIDField:
1✔
539
                        storageSPNClientID = v
1✔
540
                case storageSPNTenantIDField:
1✔
541
                        storageSPNTenantID = v
1✔
542
                case storageAADEndpointField:
1✔
543
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
544
                case getLatestAccountKeyField:
3✔
545
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
4✔
546
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
547
                        }
1✔
548
                case clientIDField:
1✔
549
                        clientID = v
1✔
550
                case mountWithWITokenField:
4✔
551
                        if mountWithWIToken, err = strconv.ParseBool(v); err != nil {
5✔
552
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", mountWithWITokenField, v)
1✔
553
                        }
1✔
554
                case tenantIDField:
1✔
555
                        tenantID = v
1✔
556
                case strings.ToLower(serviceAccountTokenField):
2✔
557
                        serviceAccountToken = v
2✔
558
                }
559
        }
560
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
12✔
561

12✔
562
        if protocol == NFS {
14✔
563
                // nfs protocol does not need account key, return directly
2✔
564
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
565
        }
2✔
566

567
        if secretNamespace == "" {
16✔
568
                if pvcNamespace == "" {
12✔
569
                        secretNamespace = defaultNamespace
6✔
570
                } else {
6✔
571
                        secretNamespace = pvcNamespace
×
572
                }
×
573
        }
574

575
        if rgName == "" {
13✔
576
                rgName = d.cloud.ResourceGroup
3✔
577
        }
3✔
578

579
        if tenantID == "" {
19✔
580
                tenantID = d.cloud.TenantID
9✔
581
        }
9✔
582

583
        if clientID != "" {
11✔
584
                if mountWithWIToken {
2✔
585
                        klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID)
1✔
586

1✔
587
                        workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken)
1✔
588
                        if err != nil {
1✔
589
                                return rgName, accountName, accountKey, containerName, authEnv, err
×
590
                        }
×
591
                        azureOAuthTokenFile := filepath.Join(defaultAzureOAuthTokenDir, clientID+accountName)
1✔
592
                        if err := os.WriteFile(azureOAuthTokenFile, []byte(workloadIdentityToken), 0600); err != nil {
1✔
593
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("failed to write workload identity token file %s: %v", azureOAuthTokenFile, err)
×
594
                        }
×
595

596
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID)
1✔
597
                        if tenantID != "" {
1✔
598
                                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID)
×
599
                        }
×
600
                        authEnv = append(authEnv, "AZURE_OAUTH_TOKEN_FILE="+azureOAuthTokenFile)
1✔
601
                        klog.V(2).Infof("workload identity auth: %v", authEnv)
1✔
602
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
603
                }
604
                klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
×
605
                if subsID == "" {
×
606
                        subsID = d.cloud.SubscriptionID
×
607
                }
×
608
                accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
×
609
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
×
610
                return rgName, accountName, accountKey, containerName, authEnv, err
×
611
        }
612

613
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
614
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
615
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
616
        if keyVaultURL != "" {
10✔
617
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
618
                if err != nil {
2✔
619
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
620
                }
1✔
621
                if isSASToken(key) {
×
622
                        accountSasToken = key
×
623
                } else {
×
624
                        accountKey = key
×
625
                }
×
626
        } else {
8✔
627
                if len(secrets) == 0 {
15✔
628
                        if secretName == "" && accountName != "" {
10✔
629
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
3✔
630
                        }
3✔
631
                        if secretName != "" {
13✔
632
                                // read from k8s secret first
6✔
633
                                var name, spnClientID, spnTenantID string
6✔
634
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
6✔
635
                                if name != "" {
6✔
636
                                        accountName = name
×
637
                                }
×
638
                                if spnClientID != "" {
6✔
639
                                        storageSPNClientID = spnClientID
×
640
                                }
×
641
                                if spnTenantID != "" {
6✔
642
                                        storageSPNTenantID = spnTenantID
×
643
                                }
×
644
                                if err != nil && strings.EqualFold(azureStorageAuthType, storageAuthTypeMSI) {
7✔
645
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
1✔
646
                                        err = nil
1✔
647
                                }
1✔
648
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
11✔
649
                                        klog.V(2).Infof("get account(%s) key from secret(%s, %s) failed with error: %v, use cluster identity to get account key instead",
5✔
650
                                                accountName, secretNamespace, secretName, err)
5✔
651
                                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
652
                                        if err != nil {
7✔
653
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
654
                                        }
2✔
655
                                }
656
                        }
657
                } else {
1✔
658
                        for k, v := range secrets {
8✔
659
                                v = strings.TrimSpace(v)
7✔
660
                                switch strings.ToLower(k) {
7✔
661
                                case accountNameField:
1✔
662
                                        accountName = v
1✔
663
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
664
                                        accountName = v
1✔
665
                                case accountKeyField:
1✔
666
                                        accountKey = v
1✔
667
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
668
                                        accountKey = v
1✔
669
                                case accountSasTokenField:
1✔
670
                                        accountSasToken = v
1✔
671
                                case msiSecretField:
1✔
672
                                        msiSecret = v
1✔
673
                                case storageSPNClientSecretField:
1✔
674
                                        storageSPNClientSecret = v
1✔
675
                                case storageSPNClientIDField:
×
676
                                        storageSPNClientID = v
×
677
                                case storageSPNTenantIDField:
×
678
                                        storageSPNTenantID = v
×
679
                                }
680
                        }
681
                }
682
        }
683

684
        if containerName == "" {
6✔
685
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
686
        }
×
687

688
        if accountKey != "" {
10✔
689
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
690
        }
4✔
691

692
        if accountSasToken != "" {
7✔
693
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
694
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
695
        }
1✔
696

697
        if msiSecret != "" {
7✔
698
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
699
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
700
        }
1✔
701

702
        if storageSPNClientSecret != "" {
7✔
703
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
704
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
705
        }
1✔
706

707
        if storageSPNClientID != "" {
6✔
708
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
709
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
710
        }
×
711

712
        if storageSPNTenantID != "" {
6✔
713
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
714
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
715
        }
×
716

717
        if azureStorageAuthType == storageAuthTypeMSI {
7✔
718
                // check whether authEnv contains AZURE_STORAGE_IDENTITY_ prefix
1✔
719
                containsIdentityEnv := false
1✔
720
                for _, env := range authEnv {
3✔
721
                        if strings.HasPrefix(env, "AZURE_STORAGE_IDENTITY_") {
2✔
722
                                klog.V(2).Infof("AZURE_STORAGE_IDENTITY_ is already set in authEnv, skip setting it again")
×
723
                                containsIdentityEnv = true
×
724
                                break
×
725
                        }
726
                }
727
                if !containsIdentityEnv && d.cloud != nil && d.cloud.Config.AzureAuthConfig.UserAssignedIdentityID != "" {
2✔
728
                        klog.V(2).Infof("azureStorageAuthType is set to %s, add AZURE_STORAGE_IDENTITY_CLIENT_ID(%s) into authEnv",
1✔
729
                                azureStorageAuthType, d.cloud.Config.AzureAuthConfig.UserAssignedIdentityID)
1✔
730
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+d.cloud.Config.AzureAuthConfig.UserAssignedIdentityID)
1✔
731
                }
1✔
732
        }
733

734
        return rgName, accountName, accountKey, containerName, authEnv, err
6✔
735
}
736

737
// GetStorageAccountAndContainer get storage account and container info
738
// returns <accountName, accountKey, accountSasToken, containerName>
739
// only for e2e testing
740
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
741
        var (
3✔
742
                subsID                string
3✔
743
                accountName           string
3✔
744
                accountKey            string
3✔
745
                accountSasToken       string
3✔
746
                containerName         string
3✔
747
                keyVaultURL           string
3✔
748
                keyVaultSecretName    string
3✔
749
                keyVaultSecretVersion string
3✔
750
                getLatestAccountKey   bool
3✔
751
                err                   error
3✔
752
        )
3✔
753

3✔
754
        for k, v := range attrib {
8✔
755
                switch strings.ToLower(k) {
5✔
756
                case subscriptionIDField:
×
757
                        subsID = v
×
758
                case containerNameField:
1✔
759
                        containerName = v
1✔
760
                case keyVaultURLField:
×
761
                        keyVaultURL = v
×
762
                case keyVaultSecretNameField:
1✔
763
                        keyVaultSecretName = v
1✔
764
                case keyVaultSecretVersionField:
1✔
765
                        keyVaultSecretVersion = v
1✔
766
                case storageAccountField:
×
767
                        accountName = v
×
768
                case storageAccountNameField: // for compatibility
1✔
769
                        accountName = v
1✔
770
                case getLatestAccountKeyField:
1✔
771
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
772
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
773
                        }
1✔
774
                }
775
        }
776

777
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
778
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
779
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
780
        if keyVaultURL != "" {
2✔
781
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
782
                if err != nil {
×
783
                        return "", "", "", "", err
×
784
                }
×
785
                if isSASToken(key) {
×
786
                        accountSasToken = key
×
787
                } else {
×
788
                        accountKey = key
×
789
                }
×
790
        } else {
2✔
791
                if len(secrets) == 0 {
4✔
792
                        var rgName string
2✔
793
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
794
                        if err != nil {
2✔
795
                                return "", "", "", "", err
×
796
                        }
×
797

798
                        if rgName == "" {
2✔
799
                                rgName = d.cloud.ResourceGroup
×
800
                        }
×
801
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
802
                        if err != nil {
3✔
803
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
804
                        }
1✔
805
                }
806
        }
807

808
        if containerName == "" {
1✔
809
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
810
        }
×
811

812
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
813
}
814

815
func IsCorruptedDir(dir string) bool {
4✔
816
        _, pathErr := mount.PathExists(dir)
4✔
817
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
818
}
4✔
819

820
func isRetriableError(err error) bool {
5✔
821
        if err != nil {
9✔
822
                for _, v := range retriableErrors {
19✔
823
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
824
                                return true
3✔
825
                        }
3✔
826
                }
827
        }
828
        return false
2✔
829
}
830

831
func isSupportedProtocol(protocol string) bool {
20✔
832
        if protocol == "" {
21✔
833
                return true
1✔
834
        }
1✔
835
        for _, v := range supportedProtocolList {
48✔
836
                if protocol == v || protocol == NFSv3 {
46✔
837
                        return true
17✔
838
                }
17✔
839
        }
840
        return false
2✔
841
}
842

843
func isSupportedAccessTier(accessTier string) bool {
23✔
844
        if accessTier == "" {
39✔
845
                return true
16✔
846
        }
16✔
847
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
848
                if accessTier == string(tier) {
28✔
849
                        return true
3✔
850
                }
3✔
851
        }
852
        return false
4✔
853
}
854

855
func isSupportedPublicNetworkAccess(publicNetworkAccess string) bool {
19✔
856
        if publicNetworkAccess == "" {
34✔
857
                return true
15✔
858
        }
15✔
859
        for _, tier := range armstorage.PossiblePublicNetworkAccessValues() {
13✔
860
                if publicNetworkAccess == string(tier) {
11✔
861
                        return true
2✔
862
                }
2✔
863
        }
864
        return false
2✔
865
}
866

867
// container names can contain only lowercase letters, numbers, and hyphens,
868
// and must begin and end with a letter or a number
869
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
870
        if prefix == "" {
34✔
871
                return true
13✔
872
        }
13✔
873
        if len(prefix) > 20 {
9✔
874
                return false
1✔
875
        }
1✔
876
        if prefix[0] == '-' {
8✔
877
                return false
1✔
878
        }
1✔
879
        for _, v := range prefix {
19✔
880
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
881
                        return false
4✔
882
                }
4✔
883
        }
884
        return true
2✔
885
}
886

887
// isNFSProtocol checks if the protocol is NFS or AZNFS
888
func isNFSProtocol(protocol string) bool {
22✔
889
        protocol = strings.ToLower(protocol)
22✔
890
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
22✔
891
}
22✔
892

893
// get storage account from secrets map
894
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
895
        if secrets == nil {
23✔
896
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
897
        }
1✔
898

899
        var accountName, accountKey string
21✔
900
        for k, v := range secrets {
64✔
901
                v = strings.TrimSpace(v)
43✔
902
                switch strings.ToLower(k) {
43✔
903
                case accountNameField:
7✔
904
                        accountName = v
7✔
905
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
906
                        accountName = v
13✔
907
                case accountKeyField:
7✔
908
                        accountKey = v
7✔
909
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
910
                        accountKey = v
12✔
911
                }
912
        }
913

914
        if accountName == "" {
25✔
915
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
916
        }
4✔
917
        if accountKey == "" {
21✔
918
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
919
        }
4✔
920

921
        accountName = strings.TrimSpace(accountName)
13✔
922
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
923
        return accountName, accountKey, nil
13✔
924
}
925

926
func getContainerReference(containerName string, secrets map[string]string, storageEndpointSuffix string) (*azstorage.Container, error) {
9✔
927
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
928
        if rerr != nil {
11✔
929
                return nil, rerr
2✔
930
        }
2✔
931
        client, err := azstorage.NewClient(accountName, accountKey, storageEndpointSuffix, azstorage.DefaultAPIVersion, true)
7✔
932
        if err != nil {
13✔
933
                return nil, err
6✔
934
        }
6✔
935
        blobClient := client.GetBlobService()
1✔
936
        container := blobClient.GetContainerReference(containerName)
1✔
937
        if container == nil {
1✔
938
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
939
        }
×
940
        return container, nil
1✔
941
}
942

943
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
944
        if kubeClient == nil {
8✔
945
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
946
                return "", nil
2✔
947
        }
2✔
948
        if accountName == "" || accountKey == "" {
6✔
949
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
950
        }
2✔
951
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
952
        secret := &v1.Secret{
2✔
953
                ObjectMeta: metav1.ObjectMeta{
2✔
954
                        Namespace: secretNamespace,
2✔
955
                        Name:      secretName,
2✔
956
                },
2✔
957
                Data: map[string][]byte{
2✔
958
                        defaultSecretAccountName: []byte(accountName),
2✔
959
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
960
                },
2✔
961
                Type: "Opaque",
2✔
962
        }
2✔
963
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
964
        if apierror.IsAlreadyExists(err) {
3✔
965
                err = nil
1✔
966
        }
1✔
967
        if err != nil {
2✔
968
                return "", fmt.Errorf("couldn't create secret %w", err)
×
969
        }
×
970
        return secretName, err
2✔
971
}
972

973
// GetStorageAccesskey get Azure storage account key from
974
//  1. secrets (if not empty)
975
//  2. use k8s client identity to read from k8s secret
976
//  3. use cluster identity to get from storage account directly
977
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storage.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
978
        if len(secrets) > 0 {
17✔
979
                return getStorageAccount(secrets)
5✔
980
        }
5✔
981

982
        // read from k8s secret first
983
        if secretName == "" {
12✔
984
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
985
        }
5✔
986
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
987
        if err != nil && d.cloud != nil {
12✔
988
                klog.V(2).Infof("could not get account(%s) key from secret(%s) namespace(%s), error: %v, use cluster identity to get account key instead", accountOptions.Name, secretName, secretNamespace, err)
5✔
989
                accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
990
        }
5✔
991
        return accountOptions.Name, accountKey, err
7✔
992
}
993

994
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
995
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
996
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
997
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
998
        }
2✔
999
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
1000
        if err != nil {
13✔
1001
                return "", err
×
1002
        }
×
1003
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
1004
}
1005

1006
// GetInfoFromSecret get info from k8s secret
1007
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
1008
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
17✔
1009
        if d.KubeClient == nil {
28✔
1010
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
11✔
1011
        }
11✔
1012

1013
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
1014
        if err != nil {
8✔
1015
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
1016
        }
2✔
1017

1018
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
1019
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
1020
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
1021
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
1022
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
1023
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
1024
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
1025

4✔
1026
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
1027
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
1028
}
1029

1030
// getSubnetResourceID get default subnet resource ID from cloud provider config
1031
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
1032
        subsID := d.cloud.SubscriptionID
6✔
1033
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
1034
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
1035
        }
4✔
1036

1037
        if len(vnetResourceGroup) == 0 {
10✔
1038
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
1039
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
1040
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
1041
                }
3✔
1042
        }
1043

1044
        if len(vnetName) == 0 {
10✔
1045
                vnetName = d.cloud.VnetName
4✔
1046
        }
4✔
1047

1048
        if len(subnetName) == 0 {
10✔
1049
                subnetName = d.cloud.SubnetName
4✔
1050
        }
4✔
1051
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
1052
}
1053

1054
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
1055
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
1056
        if err != nil {
9✔
1057
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
1058
        }
×
1059
        if cache != nil {
12✔
1060
                return true
3✔
1061
        }
3✔
1062
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1063
        if err != nil {
6✔
1064
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1065
        }
×
1066
        if cache != nil {
6✔
1067
                return true
×
1068
        }
×
1069
        return false
6✔
1070
}
1071

1072
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1073
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
6✔
1074
        var defaultMountOptions = map[string]string{
6✔
1075
                "--pre-mount-validate": "true",
6✔
1076
                "--use-https":          "true",
6✔
1077
                "--tmp-path":           tmpPath,
6✔
1078
                "--container-name":     containerName,
6✔
1079
                // prevent billing charges on mounting
6✔
1080
                "--cancel-list-on-mount-seconds": "10",
6✔
1081
                // allow remounting using a non-empty tmp-path
6✔
1082
                "--empty-dir-check": "false",
6✔
1083
        }
6✔
1084

6✔
1085
        // stores the mount options already included in mountOptions
6✔
1086
        included := make(map[string]bool)
6✔
1087

6✔
1088
        for _, mountOption := range mountOptions {
14✔
1089
                for k := range defaultMountOptions {
56✔
1090
                        if strings.HasPrefix(mountOption, k) {
52✔
1091
                                included[k] = true
4✔
1092
                        }
4✔
1093
                }
1094
        }
1095

1096
        allMountOptions := mountOptions
6✔
1097

6✔
1098
        for k, v := range defaultMountOptions {
42✔
1099
                if _, isIncluded := included[k]; !isIncluded {
68✔
1100
                        if v != "" {
63✔
1101
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
31✔
1102
                        } else {
32✔
1103
                                allMountOptions = append(allMountOptions, k)
1✔
1104
                        }
1✔
1105
                }
1106
        }
1107

1108
        return allMountOptions
6✔
1109
}
1110

1111
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1112
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
7✔
1113
        info, err := os.Lstat(targetPath)
7✔
1114
        if err != nil {
8✔
1115
                return err
1✔
1116
        }
1✔
1117
        perm := info.Mode() & os.ModePerm
6✔
1118
        expectedPerms := mode & os.ModePerm
6✔
1119
        if perm != expectedPerms {
8✔
1120
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), expectedPerms)
2✔
1121
                // only change the permission mode bits, keep the other bits as is
2✔
1122
                if err := os.Chmod(targetPath, (info.Mode()&^os.ModePerm)|os.FileMode(expectedPerms)); err != nil {
2✔
1123
                        return err
×
1124
                }
×
1125
        } else {
4✔
1126
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
4✔
1127
        }
4✔
1128
        return nil
6✔
1129
}
1130

1131
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1132
        secret := make(map[string]string)
1✔
1133
        secret[defaultSecretAccountName] = account
1✔
1134
        secret[defaultSecretAccountKey] = key
1✔
1135
        return secret
1✔
1136
}
1✔
1137

1138
// setKeyValueInMap set key/value pair in map
1139
// key in the map is case insensitive, if key already exists, overwrite existing value
1140
func setKeyValueInMap(m map[string]string, key, value string) {
10✔
1141
        if m == nil {
11✔
1142
                return
1✔
1143
        }
1✔
1144
        for k := range m {
33✔
1145
                if strings.EqualFold(k, key) {
26✔
1146
                        m[k] = value
2✔
1147
                        return
2✔
1148
                }
2✔
1149
        }
1150
        m[key] = value
7✔
1151
}
1152

1153
// getValueInMap get value from map by key
1154
// key in the map is case insensitive
1155
func getValueInMap(m map[string]string, key string) string {
18✔
1156
        if m == nil {
19✔
1157
                return ""
1✔
1158
        }
1✔
1159
        for k, v := range m {
46✔
1160
                if strings.EqualFold(k, key) {
38✔
1161
                        return v
9✔
1162
                }
9✔
1163
        }
1164
        return ""
8✔
1165
}
1166

1167
// replaceWithMap replace key with value for str
1168
func replaceWithMap(str string, m map[string]string) string {
16✔
1169
        for k, v := range m {
21✔
1170
                if k != "" {
9✔
1171
                        str = strings.ReplaceAll(str, k, v)
4✔
1172
                }
4✔
1173
        }
1174
        return str
16✔
1175
}
1176

1177
func isSupportedFSGroupChangePolicy(policy string) bool {
30✔
1178
        if policy == "" {
53✔
1179
                return true
23✔
1180
        }
23✔
1181
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1182
                if policy == v {
21✔
1183
                        return true
3✔
1184
                }
3✔
1185
        }
1186
        return false
4✔
1187
}
1188

1189
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
9✔
1190
        if vc.GetAccessMode() == nil {
11✔
1191
                return false
2✔
1192
        }
2✔
1193
        mode := vc.GetAccessMode().GetMode()
7✔
1194
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
7✔
1195
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
7✔
1196
}
1197

1198
// generateVolumeName returns a PV name with clusterName prefix. The function
1199
// should be used to generate a name of GCE PD or Cinder volume. It basically
1200
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1201
// string fits given length and cuts "dynamic" if not.
1202
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1203
        prefix := clusterName + "-dynamic"
3✔
1204
        pvLen := len(pvName)
3✔
1205
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1206
        // +1 for the '-' dash
3✔
1207
        if pvLen+1+len(prefix) > maxLength {
5✔
1208
                prefix = prefix[:maxLength-pvLen-1]
2✔
1209
        }
2✔
1210
        return prefix + "-" + pvName
3✔
1211
}
1212

1213
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1214
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1215
type serviceAccountToken struct {
1216
        APIAzureADTokenExchange struct {
1217
                Token               string    `json:"token"`
1218
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1219
        } `json:"api://AzureADTokenExchange"`
1220
}
1221

1222
// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request.
1223
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1224
func parseServiceAccountToken(tokenStr string) (string, error) {
6✔
1225
        if len(tokenStr) == 0 {
7✔
1226
                return "", fmt.Errorf("service account token is empty")
1✔
1227
        }
1✔
1228
        token := serviceAccountToken{}
5✔
1229
        if err := json.Unmarshal([]byte(tokenStr), &token); err != nil {
6✔
1230
                return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err)
1✔
1231
        }
1✔
1232
        if token.APIAzureADTokenExchange.Token == "" {
6✔
1233
                return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience)
2✔
1234
        }
2✔
1235
        return token.APIAzureADTokenExchange.Token, nil
2✔
1236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc