Skip to content

Commit 98bf4f9

Browse files
Fix DPT Azure Authentication with Velero BSL Secret Format (#1920)
Co-authored-by: Shubham Pampattiwar <[email protected]>
1 parent 7c035f4 commit 98bf4f9

File tree

2 files changed

+209
-8
lines changed

2 files changed

+209
-8
lines changed

pkg/cloudprovider/azure.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"os"
8+
"strings"
89
"time"
910

1011
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -37,16 +38,53 @@ type AzureProvider struct {
3738
client *azblob.Client
3839
}
3940

41+
// parseCloudCredentials parses environment variable format (e.g., from BSL secret 'cloud' key)
42+
func parseCloudCredentials(cloudData string) map[string]string {
43+
envVars := make(map[string]string)
44+
lines := strings.Split(cloudData, "\n")
45+
46+
for _, line := range lines {
47+
line = strings.TrimSpace(line)
48+
if line != "" && strings.Contains(line, "=") {
49+
parts := strings.SplitN(line, "=", 2)
50+
if len(parts) == 2 {
51+
key := strings.TrimSpace(parts[0])
52+
value := strings.TrimSpace(parts[1])
53+
envVars[key] = value
54+
}
55+
}
56+
}
57+
58+
return envVars
59+
}
60+
4061
func ParseAzureCredentials(data map[string][]byte) AzureCredentials {
4162
creds := AzureCredentials{}
42-
creds.SubscriptionID = string(data["AZURE_SUBSCRIPTION_ID"])
43-
creds.TenantID = string(data["AZURE_TENANT_ID"])
44-
creds.ClientID = string(data["AZURE_CLIENT_ID"])
45-
creds.ClientSecret = string(data["AZURE_CLIENT_SECRET"])
46-
creds.ResourceGroupName = string(data["AZURE_RESOURCE_GROUP"])
47-
creds.StorageAccountName = string(data["AZURE_STORAGE_ACCOUNT_ID"])
48-
creds.StorageAccountKey = string(data["AZURE_STORAGE_ACCOUNT_ACCESS_KEY"])
49-
creds.CertificatePath = string(data["AZURE_CLIENT_CERTIFICATE_PATH"])
63+
64+
// Check if we have the 'cloud' key (Velero BSL format)
65+
if cloudData, exists := data["cloud"]; exists && len(cloudData) > 0 {
66+
envVars := parseCloudCredentials(string(cloudData))
67+
68+
creds.SubscriptionID = envVars["AZURE_SUBSCRIPTION_ID"]
69+
creds.TenantID = envVars["AZURE_TENANT_ID"]
70+
creds.ClientID = envVars["AZURE_CLIENT_ID"]
71+
creds.ClientSecret = envVars["AZURE_CLIENT_SECRET"]
72+
creds.ResourceGroupName = envVars["AZURE_RESOURCE_GROUP"]
73+
creds.StorageAccountName = envVars["AZURE_STORAGE_ACCOUNT_ID"]
74+
creds.StorageAccountKey = envVars["AZURE_STORAGE_ACCOUNT_ACCESS_KEY"]
75+
creds.CertificatePath = envVars["AZURE_CLIENT_CERTIFICATE_PATH"]
76+
} else {
77+
// Fall back to individual keys format
78+
creds.SubscriptionID = string(data["AZURE_SUBSCRIPTION_ID"])
79+
creds.TenantID = string(data["AZURE_TENANT_ID"])
80+
creds.ClientID = string(data["AZURE_CLIENT_ID"])
81+
creds.ClientSecret = string(data["AZURE_CLIENT_SECRET"])
82+
creds.ResourceGroupName = string(data["AZURE_RESOURCE_GROUP"])
83+
creds.StorageAccountName = string(data["AZURE_STORAGE_ACCOUNT_ID"])
84+
creds.StorageAccountKey = string(data["AZURE_STORAGE_ACCOUNT_ACCESS_KEY"])
85+
creds.CertificatePath = string(data["AZURE_CLIENT_CERTIFICATE_PATH"])
86+
}
87+
5088
return creds
5189
}
5290

pkg/cloudprovider/azure_test.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package cloudprovider
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseAzureCredentials_CloudKeyFormat(t *testing.T) {
8+
// Test Velero BSL secret format (single 'cloud' key with env vars)
9+
cloudContent := `AZURE_SUBSCRIPTION_ID=test-subscription-id
10+
AZURE_TENANT_ID=test-tenant-id
11+
AZURE_CLIENT_ID=test-client-id
12+
AZURE_CLIENT_SECRET=test-client-secret
13+
AZURE_RESOURCE_GROUP=test-rg
14+
AZURE_STORAGE_ACCOUNT_ID=teststorageaccount`
15+
16+
data := map[string][]byte{
17+
"cloud": []byte(cloudContent),
18+
}
19+
20+
creds := ParseAzureCredentials(data)
21+
22+
if creds.SubscriptionID != "test-subscription-id" {
23+
t.Errorf("Expected SubscriptionID to be 'test-subscription-id', got '%s'", creds.SubscriptionID)
24+
}
25+
if creds.TenantID != "test-tenant-id" {
26+
t.Errorf("Expected TenantID to be 'test-tenant-id', got '%s'", creds.TenantID)
27+
}
28+
if creds.ClientID != "test-client-id" {
29+
t.Errorf("Expected ClientID to be 'test-client-id', got '%s'", creds.ClientID)
30+
}
31+
if creds.ClientSecret != "test-client-secret" {
32+
t.Errorf("Expected ClientSecret to be 'test-client-secret', got '%s'", creds.ClientSecret)
33+
}
34+
if creds.ResourceGroupName != "test-rg" {
35+
t.Errorf("Expected ResourceGroupName to be 'test-rg', got '%s'", creds.ResourceGroupName)
36+
}
37+
if creds.StorageAccountName != "teststorageaccount" {
38+
t.Errorf("Expected StorageAccountName to be 'teststorageaccount', got '%s'", creds.StorageAccountName)
39+
}
40+
}
41+
42+
func TestParseAzureCredentials_IndividualKeysFormat(t *testing.T) {
43+
// Test individual keys format (existing DPT format)
44+
data := map[string][]byte{
45+
"AZURE_SUBSCRIPTION_ID": []byte("test-subscription-id"),
46+
"AZURE_TENANT_ID": []byte("test-tenant-id"),
47+
"AZURE_CLIENT_ID": []byte("test-client-id"),
48+
"AZURE_CLIENT_SECRET": []byte("test-client-secret"),
49+
"AZURE_RESOURCE_GROUP": []byte("test-rg"),
50+
"AZURE_STORAGE_ACCOUNT_ID": []byte("teststorageaccount"),
51+
}
52+
53+
creds := ParseAzureCredentials(data)
54+
55+
if creds.SubscriptionID != "test-subscription-id" {
56+
t.Errorf("Expected SubscriptionID to be 'test-subscription-id', got '%s'", creds.SubscriptionID)
57+
}
58+
if creds.TenantID != "test-tenant-id" {
59+
t.Errorf("Expected TenantID to be 'test-tenant-id', got '%s'", creds.TenantID)
60+
}
61+
if creds.ClientID != "test-client-id" {
62+
t.Errorf("Expected ClientID to be 'test-client-id', got '%s'", creds.ClientID)
63+
}
64+
if creds.ClientSecret != "test-client-secret" {
65+
t.Errorf("Expected ClientSecret to be 'test-client-secret', got '%s'", creds.ClientSecret)
66+
}
67+
if creds.ResourceGroupName != "test-rg" {
68+
t.Errorf("Expected ResourceGroupName to be 'test-rg', got '%s'", creds.ResourceGroupName)
69+
}
70+
if creds.StorageAccountName != "teststorageaccount" {
71+
t.Errorf("Expected StorageAccountName to be 'teststorageaccount', got '%s'", creds.StorageAccountName)
72+
}
73+
}
74+
75+
func TestParseAzureCredentials_EmptyCloudKey(t *testing.T) {
76+
// Test with empty cloud key should fall back to individual keys
77+
data := map[string][]byte{
78+
"cloud": []byte(""),
79+
"AZURE_SUBSCRIPTION_ID": []byte("test-subscription"),
80+
"AZURE_TENANT_ID": []byte("test-tenant"),
81+
}
82+
83+
creds := ParseAzureCredentials(data)
84+
85+
if creds.SubscriptionID != "test-subscription" {
86+
t.Errorf("Expected SubscriptionID to be 'test-subscription', got '%s'", creds.SubscriptionID)
87+
}
88+
if creds.TenantID != "test-tenant" {
89+
t.Errorf("Expected TenantID to be 'test-tenant', got '%s'", creds.TenantID)
90+
}
91+
}
92+
93+
func TestParseAzureCredentials_StorageAccountKeyFormat(t *testing.T) {
94+
// Test Velero BSL secret format with storage account key authentication
95+
cloudContent := `AZURE_SUBSCRIPTION_ID=test-subscription-id
96+
AZURE_TENANT_ID=test-tenant-id
97+
AZURE_CLIENT_ID=test-client-id
98+
AZURE_CLIENT_SECRET=test-client-secret
99+
AZURE_RESOURCE_GROUP=test-rg
100+
AZURE_STORAGE_ACCOUNT_ID=teststorageaccount
101+
AZURE_STORAGE_ACCOUNT_ACCESS_KEY=test-storage-key`
102+
103+
data := map[string][]byte{
104+
"cloud": []byte(cloudContent),
105+
}
106+
107+
creds := ParseAzureCredentials(data)
108+
109+
// Verify all fields are parsed correctly for storage account key auth
110+
if creds.SubscriptionID != "test-subscription-id" {
111+
t.Errorf("Expected SubscriptionID to be 'test-subscription-id', got '%s'", creds.SubscriptionID)
112+
}
113+
if creds.TenantID != "test-tenant-id" {
114+
t.Errorf("Expected TenantID to be 'test-tenant-id', got '%s'", creds.TenantID)
115+
}
116+
if creds.ClientID != "test-client-id" {
117+
t.Errorf("Expected ClientID to be 'test-client-id', got '%s'", creds.ClientID)
118+
}
119+
if creds.ClientSecret != "test-client-secret" {
120+
t.Errorf("Expected ClientSecret to be 'test-client-secret', got '%s'", creds.ClientSecret)
121+
}
122+
if creds.ResourceGroupName != "test-rg" {
123+
t.Errorf("Expected ResourceGroupName to be 'test-rg', got '%s'", creds.ResourceGroupName)
124+
}
125+
if creds.StorageAccountName != "teststorageaccount" {
126+
t.Errorf("Expected StorageAccountName to be 'teststorageaccount', got '%s'", creds.StorageAccountName)
127+
}
128+
if creds.StorageAccountKey != "test-storage-key" {
129+
t.Errorf("Expected StorageAccountKey to be 'test-storage-key', got '%s'", creds.StorageAccountKey)
130+
}
131+
}
132+
133+
func TestParseCloudCredentials(t *testing.T) {
134+
cloudData := `AZURE_SUBSCRIPTION_ID=test-sub
135+
AZURE_TENANT_ID=test-tenant
136+
137+
# This is a comment
138+
AZURE_CLIENT_ID=test-client
139+
INVALID_LINE_WITHOUT_EQUALS
140+
AZURE_CLIENT_SECRET=test-secret`
141+
142+
envVars := parseCloudCredentials(cloudData)
143+
144+
expected := map[string]string{
145+
"AZURE_SUBSCRIPTION_ID": "test-sub",
146+
"AZURE_TENANT_ID": "test-tenant",
147+
"AZURE_CLIENT_ID": "test-client",
148+
"AZURE_CLIENT_SECRET": "test-secret",
149+
}
150+
151+
for key, expectedValue := range expected {
152+
if value, exists := envVars[key]; !exists {
153+
t.Errorf("Expected key '%s' to exist", key)
154+
} else if value != expectedValue {
155+
t.Errorf("Expected %s to be '%s', got '%s'", key, expectedValue, value)
156+
}
157+
}
158+
159+
// Should not include invalid lines
160+
if _, exists := envVars["INVALID_LINE_WITHOUT_EQUALS"]; exists {
161+
t.Error("Should not include invalid lines without equals sign")
162+
}
163+
}

0 commit comments

Comments
 (0)