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

kubernetes-sigs / blob-csi-driver / 14864807864

06 May 2025 04:27PM UTC coverage: 79.841%. Remained the same
14864807864

Pull #1990

github

web-flow
chore(deps): bump golang.org/x/net from 0.39.0 to 0.40.0

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/net/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1990: chore(deps): bump golang.org/x/net from 0.39.0 to 0.40.0

2408 of 3016 relevant lines covered (79.84%)

7.98 hits per line

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

86.7
/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
        "strconv"
27
        "strings"
28
        "sync"
29
        "time"
30

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

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

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

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

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

139
        // 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
140
        containerMaxSize = 100 * util.TiB
141

142
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
143

144
        defaultNamespace = "default"
145

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

153
        VolumeID = "volumeid"
154

155
        defaultStorageEndPointSuffix = "core.windows.net"
156

157
        FSGroupChangeNone = "None"
158
        // define tag value delimiter and default is comma
159
        tagValueDelimiterField = "tagvaluedelimiter"
160

161
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
162

163
)
164

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

170
        // 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,
171
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
172
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
173
)
174

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

196
func (option *DriverOptions) AddFlags() {
1✔
197
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
198
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
199
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
200
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
201
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
202
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
203
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
204
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
205
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
206
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
207
        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✔
208
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
209
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
210
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
211
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
212
        flag.BoolVar(&option.EnableVolumeMountGroup, "enable-volume-mount-group", true, "indicates whether enabling VOLUME_MOUNT_GROUP")
1✔
213
        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✔
214
}
1✔
215

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

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

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

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

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

326
        d.mounter = &mount.SafeFormatAndMount{
139✔
327
                Interface: mount.New(""),
139✔
328
                Exec:      utilexec.New(),
139✔
329
        }
139✔
330

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

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

139✔
363
        return &d
139✔
364
}
365

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

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

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

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

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

457
        return false
2✔
458
}
459

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

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

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

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

11✔
554
        if protocol == NFS {
13✔
555
                // nfs protocol does not need account key, return directly
2✔
556
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
557
        }
2✔
558

559
        if secretNamespace == "" {
16✔
560
                if pvcNamespace == "" {
14✔
561
                        secretNamespace = defaultNamespace
7✔
562
                } else {
7✔
563
                        secretNamespace = pvcNamespace
×
564
                }
×
565
        }
566

567
        if rgName == "" {
12✔
568
                rgName = d.cloud.ResourceGroup
3✔
569
        }
3✔
570

571
        if tenantID == "" {
18✔
572
                tenantID = d.cloud.TenantID
9✔
573
        }
9✔
574

575
        // if client id is specified, we only use workload identity for blobfuse auth
576
        if clientID != "" {
10✔
577
                klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID)
1✔
578

1✔
579
                workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken)
1✔
580
                if err != nil {
1✔
581
                        return rgName, accountName, accountKey, containerName, authEnv, err
×
582
                }
×
583

584
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID)
1✔
585
                if tenantID != "" {
1✔
586
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID)
×
587
                }
×
588
                authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workloadIdentityToken)
1✔
589

1✔
590
                return rgName, accountName, accountKey, containerName, authEnv, err
1✔
591
        }
592

593
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
594
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
595
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
596
        if keyVaultURL != "" {
9✔
597
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
598
                if err != nil {
2✔
599
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
600
                }
1✔
601
                if isSASToken(key) {
×
602
                        accountSasToken = key
×
603
                } else {
×
604
                        accountKey = key
×
605
                }
×
606
        } else {
7✔
607
                if len(secrets) == 0 {
13✔
608
                        if secretName == "" && accountName != "" {
10✔
609
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
610
                        }
4✔
611
                        if secretName != "" {
11✔
612
                                // read from k8s secret first
5✔
613
                                var name, spnClientID, spnTenantID string
5✔
614
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
615
                                if name != "" {
5✔
616
                                        accountName = name
×
617
                                }
×
618
                                if spnClientID != "" {
5✔
619
                                        storageSPNClientID = spnClientID
×
620
                                }
×
621
                                if spnTenantID != "" {
5✔
622
                                        storageSPNTenantID = spnTenantID
×
623
                                }
×
624
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
625
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
626
                                        err = nil
×
627
                                }
×
628
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
629
                                        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✔
630
                                                accountName, secretNamespace, secretName, err)
5✔
631
                                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
632
                                        if err != nil {
7✔
633
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
634
                                        }
2✔
635
                                }
636
                        }
637
                } else {
1✔
638
                        for k, v := range secrets {
8✔
639
                                v = strings.TrimSpace(v)
7✔
640
                                switch strings.ToLower(k) {
7✔
641
                                case accountNameField:
1✔
642
                                        accountName = v
1✔
643
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
644
                                        accountName = v
1✔
645
                                case accountKeyField:
1✔
646
                                        accountKey = v
1✔
647
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
648
                                        accountKey = v
1✔
649
                                case accountSasTokenField:
1✔
650
                                        accountSasToken = v
1✔
651
                                case msiSecretField:
1✔
652
                                        msiSecret = v
1✔
653
                                case storageSPNClientSecretField:
1✔
654
                                        storageSPNClientSecret = v
1✔
655
                                case storageSPNClientIDField:
×
656
                                        storageSPNClientID = v
×
657
                                case storageSPNTenantIDField:
×
658
                                        storageSPNTenantID = v
×
659
                                }
660
                        }
661
                }
662
        }
663

664
        if containerName == "" {
5✔
665
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
666
        }
×
667

668
        if accountKey != "" {
9✔
669
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
670
        }
4✔
671

672
        if accountSasToken != "" {
6✔
673
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
674
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
675
        }
1✔
676

677
        if msiSecret != "" {
6✔
678
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
679
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
680
        }
1✔
681

682
        if storageSPNClientSecret != "" {
6✔
683
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
684
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
685
        }
1✔
686

687
        if storageSPNClientID != "" {
5✔
688
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
689
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
690
        }
×
691

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

697
        return rgName, accountName, accountKey, containerName, authEnv, err
5✔
698
}
699

700
// GetStorageAccountAndContainer get storage account and container info
701
// returns <accountName, accountKey, accountSasToken, containerName>
702
// only for e2e testing
703
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
704
        var (
3✔
705
                subsID                string
3✔
706
                accountName           string
3✔
707
                accountKey            string
3✔
708
                accountSasToken       string
3✔
709
                containerName         string
3✔
710
                keyVaultURL           string
3✔
711
                keyVaultSecretName    string
3✔
712
                keyVaultSecretVersion string
3✔
713
                getLatestAccountKey   bool
3✔
714
                err                   error
3✔
715
        )
3✔
716

3✔
717
        for k, v := range attrib {
8✔
718
                switch strings.ToLower(k) {
5✔
719
                case subscriptionIDField:
×
720
                        subsID = v
×
721
                case containerNameField:
1✔
722
                        containerName = v
1✔
723
                case keyVaultURLField:
×
724
                        keyVaultURL = v
×
725
                case keyVaultSecretNameField:
1✔
726
                        keyVaultSecretName = v
1✔
727
                case keyVaultSecretVersionField:
1✔
728
                        keyVaultSecretVersion = v
1✔
729
                case storageAccountField:
×
730
                        accountName = v
×
731
                case storageAccountNameField: // for compatibility
1✔
732
                        accountName = v
1✔
733
                case getLatestAccountKeyField:
1✔
734
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
735
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
736
                        }
1✔
737
                }
738
        }
739

740
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
741
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
742
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
743
        if keyVaultURL != "" {
2✔
744
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
745
                if err != nil {
×
746
                        return "", "", "", "", err
×
747
                }
×
748
                if isSASToken(key) {
×
749
                        accountSasToken = key
×
750
                } else {
×
751
                        accountKey = key
×
752
                }
×
753
        } else {
2✔
754
                if len(secrets) == 0 {
4✔
755
                        var rgName string
2✔
756
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
757
                        if err != nil {
2✔
758
                                return "", "", "", "", err
×
759
                        }
×
760

761
                        if rgName == "" {
2✔
762
                                rgName = d.cloud.ResourceGroup
×
763
                        }
×
764
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
765
                        if err != nil {
3✔
766
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
767
                        }
1✔
768
                }
769
        }
770

771
        if containerName == "" {
1✔
772
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
773
        }
×
774

775
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
776
}
777

778
func IsCorruptedDir(dir string) bool {
4✔
779
        _, pathErr := mount.PathExists(dir)
4✔
780
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
781
}
4✔
782

783
func isRetriableError(err error) bool {
5✔
784
        if err != nil {
9✔
785
                for _, v := range retriableErrors {
19✔
786
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
787
                                return true
3✔
788
                        }
3✔
789
                }
790
        }
791
        return false
2✔
792
}
793

794
func isSupportedProtocol(protocol string) bool {
20✔
795
        if protocol == "" {
21✔
796
                return true
1✔
797
        }
1✔
798
        for _, v := range supportedProtocolList {
48✔
799
                if protocol == v || protocol == NFSv3 {
46✔
800
                        return true
17✔
801
                }
17✔
802
        }
803
        return false
2✔
804
}
805

806
func isSupportedAccessTier(accessTier string) bool {
23✔
807
        if accessTier == "" {
39✔
808
                return true
16✔
809
        }
16✔
810
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
811
                if accessTier == string(tier) {
28✔
812
                        return true
3✔
813
                }
3✔
814
        }
815
        return false
4✔
816
}
817

818
func isSupportedPublicNetworkAccess(publicNetworkAccess string) bool {
19✔
819
        if publicNetworkAccess == "" {
34✔
820
                return true
15✔
821
        }
15✔
822
        for _, tier := range armstorage.PossiblePublicNetworkAccessValues() {
13✔
823
                if publicNetworkAccess == string(tier) {
11✔
824
                        return true
2✔
825
                }
2✔
826
        }
827
        return false
2✔
828
}
829

830
// container names can contain only lowercase letters, numbers, and hyphens,
831
// and must begin and end with a letter or a number
832
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
833
        if prefix == "" {
34✔
834
                return true
13✔
835
        }
13✔
836
        if len(prefix) > 20 {
9✔
837
                return false
1✔
838
        }
1✔
839
        if prefix[0] == '-' {
8✔
840
                return false
1✔
841
        }
1✔
842
        for _, v := range prefix {
19✔
843
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
844
                        return false
4✔
845
                }
4✔
846
        }
847
        return true
2✔
848
}
849

850
// isNFSProtocol checks if the protocol is NFS or AZNFS
851
func isNFSProtocol(protocol string) bool {
22✔
852
        protocol = strings.ToLower(protocol)
22✔
853
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
22✔
854
}
22✔
855

856
// get storage account from secrets map
857
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
858
        if secrets == nil {
23✔
859
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
860
        }
1✔
861

862
        var accountName, accountKey string
21✔
863
        for k, v := range secrets {
64✔
864
                v = strings.TrimSpace(v)
43✔
865
                switch strings.ToLower(k) {
43✔
866
                case accountNameField:
7✔
867
                        accountName = v
7✔
868
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
869
                        accountName = v
13✔
870
                case accountKeyField:
7✔
871
                        accountKey = v
7✔
872
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
873
                        accountKey = v
12✔
874
                }
875
        }
876

877
        if accountName == "" {
25✔
878
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
879
        }
4✔
880
        if accountKey == "" {
21✔
881
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
882
        }
4✔
883

884
        accountName = strings.TrimSpace(accountName)
13✔
885
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
886
        return accountName, accountKey, nil
13✔
887
}
888

889
func getContainerReference(containerName string, secrets map[string]string, storageEndpointSuffix string) (*azstorage.Container, error) {
9✔
890
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
891
        if rerr != nil {
11✔
892
                return nil, rerr
2✔
893
        }
2✔
894
        client, err := azstorage.NewClient(accountName, accountKey, storageEndpointSuffix, azstorage.DefaultAPIVersion, true)
7✔
895
        if err != nil {
13✔
896
                return nil, err
6✔
897
        }
6✔
898
        blobClient := client.GetBlobService()
1✔
899
        container := blobClient.GetContainerReference(containerName)
1✔
900
        if container == nil {
1✔
901
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
902
        }
×
903
        return container, nil
1✔
904
}
905

906
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
907
        if kubeClient == nil {
8✔
908
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
909
                return "", nil
2✔
910
        }
2✔
911
        if accountName == "" || accountKey == "" {
6✔
912
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
913
        }
2✔
914
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
915
        secret := &v1.Secret{
2✔
916
                ObjectMeta: metav1.ObjectMeta{
2✔
917
                        Namespace: secretNamespace,
2✔
918
                        Name:      secretName,
2✔
919
                },
2✔
920
                Data: map[string][]byte{
2✔
921
                        defaultSecretAccountName: []byte(accountName),
2✔
922
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
923
                },
2✔
924
                Type: "Opaque",
2✔
925
        }
2✔
926
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
927
        if apierror.IsAlreadyExists(err) {
3✔
928
                err = nil
1✔
929
        }
1✔
930
        if err != nil {
2✔
931
                return "", fmt.Errorf("couldn't create secret %w", err)
×
932
        }
×
933
        return secretName, err
2✔
934
}
935

936
// GetStorageAccesskey get Azure storage account key from
937
//  1. secrets (if not empty)
938
//  2. use k8s client identity to read from k8s secret
939
//  3. use cluster identity to get from storage account directly
940
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storage.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
941
        if len(secrets) > 0 {
17✔
942
                return getStorageAccount(secrets)
5✔
943
        }
5✔
944

945
        // read from k8s secret first
946
        if secretName == "" {
12✔
947
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
948
        }
5✔
949
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
950
        if err != nil && d.cloud != nil {
12✔
951
                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✔
952
                accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
953
        }
5✔
954
        return accountOptions.Name, accountKey, err
7✔
955
}
956

957
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
958
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
959
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
960
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
961
        }
2✔
962
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
963
        if err != nil {
13✔
964
                return "", err
×
965
        }
×
966
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
967
}
968

969
// GetInfoFromSecret get info from k8s secret
970
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
971
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
972
        if d.KubeClient == nil {
26✔
973
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
974
        }
10✔
975

976
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
977
        if err != nil {
8✔
978
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
979
        }
2✔
980

981
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
982
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
983
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
984
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
985
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
986
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
987
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
988

4✔
989
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
990
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
991
}
992

993
// getSubnetResourceID get default subnet resource ID from cloud provider config
994
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
995
        subsID := d.cloud.SubscriptionID
6✔
996
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
997
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
998
        }
4✔
999

1000
        if len(vnetResourceGroup) == 0 {
10✔
1001
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
1002
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
1003
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
1004
                }
3✔
1005
        }
1006

1007
        if len(vnetName) == 0 {
10✔
1008
                vnetName = d.cloud.VnetName
4✔
1009
        }
4✔
1010

1011
        if len(subnetName) == 0 {
10✔
1012
                subnetName = d.cloud.SubnetName
4✔
1013
        }
4✔
1014
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
1015
}
1016

1017
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
1018
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
1019
        if err != nil {
9✔
1020
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
1021
        }
×
1022
        if cache != nil {
12✔
1023
                return true
3✔
1024
        }
3✔
1025
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1026
        if err != nil {
6✔
1027
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1028
        }
×
1029
        if cache != nil {
6✔
1030
                return true
×
1031
        }
×
1032
        return false
6✔
1033
}
1034

1035
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1036
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
6✔
1037
        var defaultMountOptions = map[string]string{
6✔
1038
                "--pre-mount-validate": "true",
6✔
1039
                "--use-https":          "true",
6✔
1040
                "--tmp-path":           tmpPath,
6✔
1041
                "--container-name":     containerName,
6✔
1042
                // prevent billing charges on mounting
6✔
1043
                "--cancel-list-on-mount-seconds": "10",
6✔
1044
                // allow remounting using a non-empty tmp-path
6✔
1045
                "--empty-dir-check": "false",
6✔
1046
        }
6✔
1047

6✔
1048
        // stores the mount options already included in mountOptions
6✔
1049
        included := make(map[string]bool)
6✔
1050

6✔
1051
        for _, mountOption := range mountOptions {
14✔
1052
                for k := range defaultMountOptions {
56✔
1053
                        if strings.HasPrefix(mountOption, k) {
52✔
1054
                                included[k] = true
4✔
1055
                        }
4✔
1056
                }
1057
        }
1058

1059
        allMountOptions := mountOptions
6✔
1060

6✔
1061
        for k, v := range defaultMountOptions {
42✔
1062
                if _, isIncluded := included[k]; !isIncluded {
68✔
1063
                        if v != "" {
63✔
1064
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
31✔
1065
                        } else {
32✔
1066
                                allMountOptions = append(allMountOptions, k)
1✔
1067
                        }
1✔
1068
                }
1069
        }
1070

1071
        return allMountOptions
6✔
1072
}
1073

1074
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1075
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1076
        info, err := os.Lstat(targetPath)
3✔
1077
        if err != nil {
4✔
1078
                return err
1✔
1079
        }
1✔
1080
        perm := info.Mode() & os.ModePerm
2✔
1081
        if perm != mode {
3✔
1082
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1083
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1084
                        return err
×
1085
                }
×
1086
        } else {
1✔
1087
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1088
        }
1✔
1089
        return nil
2✔
1090
}
1091

1092
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1093
        secret := make(map[string]string)
1✔
1094
        secret[defaultSecretAccountName] = account
1✔
1095
        secret[defaultSecretAccountKey] = key
1✔
1096
        return secret
1✔
1097
}
1✔
1098

1099
// setKeyValueInMap set key/value pair in map
1100
// key in the map is case insensitive, if key already exists, overwrite existing value
1101
func setKeyValueInMap(m map[string]string, key, value string) {
10✔
1102
        if m == nil {
11✔
1103
                return
1✔
1104
        }
1✔
1105
        for k := range m {
33✔
1106
                if strings.EqualFold(k, key) {
26✔
1107
                        m[k] = value
2✔
1108
                        return
2✔
1109
                }
2✔
1110
        }
1111
        m[key] = value
7✔
1112
}
1113

1114
// getValueInMap get value from map by key
1115
// key in the map is case insensitive
1116
func getValueInMap(m map[string]string, key string) string {
18✔
1117
        if m == nil {
19✔
1118
                return ""
1✔
1119
        }
1✔
1120
        for k, v := range m {
43✔
1121
                if strings.EqualFold(k, key) {
35✔
1122
                        return v
9✔
1123
                }
9✔
1124
        }
1125
        return ""
8✔
1126
}
1127

1128
// replaceWithMap replace key with value for str
1129
func replaceWithMap(str string, m map[string]string) string {
16✔
1130
        for k, v := range m {
21✔
1131
                if k != "" {
9✔
1132
                        str = strings.ReplaceAll(str, k, v)
4✔
1133
                }
4✔
1134
        }
1135
        return str
16✔
1136
}
1137

1138
func isSupportedFSGroupChangePolicy(policy string) bool {
30✔
1139
        if policy == "" {
53✔
1140
                return true
23✔
1141
        }
23✔
1142
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1143
                if policy == v {
21✔
1144
                        return true
3✔
1145
                }
3✔
1146
        }
1147
        return false
4✔
1148
}
1149

1150
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
9✔
1151
        if vc.GetAccessMode() == nil {
11✔
1152
                return false
2✔
1153
        }
2✔
1154
        mode := vc.GetAccessMode().GetMode()
7✔
1155
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
7✔
1156
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
7✔
1157
}
1158

1159
// generateVolumeName returns a PV name with clusterName prefix. The function
1160
// should be used to generate a name of GCE PD or Cinder volume. It basically
1161
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1162
// string fits given length and cuts "dynamic" if not.
1163
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1164
        prefix := clusterName + "-dynamic"
3✔
1165
        pvLen := len(pvName)
3✔
1166
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1167
        // +1 for the '-' dash
3✔
1168
        if pvLen+1+len(prefix) > maxLength {
5✔
1169
                prefix = prefix[:maxLength-pvLen-1]
2✔
1170
        }
2✔
1171
        return prefix + "-" + pvName
3✔
1172
}
1173

1174
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1175
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1176
type serviceAccountToken struct {
1177
        APIAzureADTokenExchange struct {
1178
                Token               string    `json:"token"`
1179
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1180
        } `json:"api://AzureADTokenExchange"`
1181
}
1182

1183
// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request.
1184
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1185
func parseServiceAccountToken(tokenStr string) (string, error) {
6✔
1186
        if len(tokenStr) == 0 {
7✔
1187
                return "", fmt.Errorf("service account token is empty")
1✔
1188
        }
1✔
1189
        token := serviceAccountToken{}
5✔
1190
        if err := json.Unmarshal([]byte(tokenStr), &token); err != nil {
6✔
1191
                return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err)
1✔
1192
        }
1✔
1193
        if token.APIAzureADTokenExchange.Token == "" {
6✔
1194
                return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience)
2✔
1195
        }
2✔
1196
        return token.APIAzureADTokenExchange.Token, nil
2✔
1197
}
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