diff --git a/docs/api-references/docs.md b/docs/api-references/docs.md index 7bb15cf2e1d..6a96a6bbcc0 100644 --- a/docs/api-references/docs.md +++ b/docs/api-references/docs.md @@ -3394,6 +3394,30 @@ azblob service account credentials.
storageAccount
+
+string
+
+StorageAccount is the storage account of the azure blob storage +If this field is set, then use this to set backup-manager env +Otherwise retrieve the storage account from secret
+sasToken
+
+string
+
+SasToken is the sas token of the storage account
+prefix
string
diff --git a/manifests/crd.yaml b/manifests/crd.yaml
index 5af06877636..49fed4d885a 100644
--- a/manifests/crd.yaml
+++ b/manifests/crd.yaml
@@ -1187,8 +1187,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
@@ -3603,8 +3607,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
@@ -5832,8 +5840,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
@@ -17214,8 +17226,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backupType:
type: string
@@ -18101,8 +18117,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
gcs:
properties:
diff --git a/manifests/crd/federation/v1/federation.pingcap.com_volumebackups.yaml b/manifests/crd/federation/v1/federation.pingcap.com_volumebackups.yaml
index 528c603a8a0..3f19f9e50f2 100644
--- a/manifests/crd/federation/v1/federation.pingcap.com_volumebackups.yaml
+++ b/manifests/crd/federation/v1/federation.pingcap.com_volumebackups.yaml
@@ -805,8 +805,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
br:
properties:
diff --git a/manifests/crd/federation/v1/federation.pingcap.com_volumebackupschedules.yaml b/manifests/crd/federation/v1/federation.pingcap.com_volumebackupschedules.yaml
index 6cf487f81a0..2c7e482bcd9 100644
--- a/manifests/crd/federation/v1/federation.pingcap.com_volumebackupschedules.yaml
+++ b/manifests/crd/federation/v1/federation.pingcap.com_volumebackupschedules.yaml
@@ -810,8 +810,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
br:
properties:
diff --git a/manifests/crd/federation/v1/federation.pingcap.com_volumerestores.yaml b/manifests/crd/federation/v1/federation.pingcap.com_volumerestores.yaml
index d2961345965..f31b965a7ab 100644
--- a/manifests/crd/federation/v1/federation.pingcap.com_volumerestores.yaml
+++ b/manifests/crd/federation/v1/federation.pingcap.com_volumerestores.yaml
@@ -61,8 +61,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
gcs:
properties:
diff --git a/manifests/crd/v1/pingcap.com_backups.yaml b/manifests/crd/v1/pingcap.com_backups.yaml
index 8d0d559c3d6..753a1d33caf 100644
--- a/manifests/crd/v1/pingcap.com_backups.yaml
+++ b/manifests/crd/v1/pingcap.com_backups.yaml
@@ -1187,8 +1187,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
diff --git a/manifests/crd/v1/pingcap.com_backupschedules.yaml b/manifests/crd/v1/pingcap.com_backupschedules.yaml
index 16fdcad49f0..fadc331a7dc 100644
--- a/manifests/crd/v1/pingcap.com_backupschedules.yaml
+++ b/manifests/crd/v1/pingcap.com_backupschedules.yaml
@@ -1162,8 +1162,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
@@ -3391,8 +3395,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backoffRetryPolicy:
properties:
diff --git a/manifests/crd/v1/pingcap.com_restores.yaml b/manifests/crd/v1/pingcap.com_restores.yaml
index 4bed2fca5ee..06de50d03b9 100644
--- a/manifests/crd/v1/pingcap.com_restores.yaml
+++ b/manifests/crd/v1/pingcap.com_restores.yaml
@@ -1160,8 +1160,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
backupType:
type: string
@@ -2047,8 +2051,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
gcs:
properties:
diff --git a/manifests/federation-crd.yaml b/manifests/federation-crd.yaml
index fa7726482cd..0a6ae36d687 100644
--- a/manifests/federation-crd.yaml
+++ b/manifests/federation-crd.yaml
@@ -805,8 +805,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
br:
properties:
@@ -2638,8 +2642,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
br:
properties:
@@ -3674,8 +3682,12 @@ spec:
type: string
prefix:
type: string
+ sasToken:
+ type: string
secretName:
type: string
+ storageAccount:
+ type: string
type: object
gcs:
properties:
diff --git a/pkg/apis/pingcap/v1alpha1/openapi_generated.go b/pkg/apis/pingcap/v1alpha1/openapi_generated.go
index aadde10b971..85c0ec12465 100644
--- a/pkg/apis/pingcap/v1alpha1/openapi_generated.go
+++ b/pkg/apis/pingcap/v1alpha1/openapi_generated.go
@@ -580,6 +580,20 @@ func schema_pkg_apis_pingcap_v1alpha1_AzblobStorageProvider(ref common.Reference
Format: "",
},
},
+ "storageAccount": {
+ SchemaProps: spec.SchemaProps{
+ Description: "StorageAccount is the storage account of the azure blob storage If this field is set, then use this to set backup-manager env Otherwise retrieve the storage account from secret",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "sasToken": {
+ SchemaProps: spec.SchemaProps{
+ Description: "SasToken is the sas token of the storage account",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
"prefix": {
SchemaProps: spec.SchemaProps{
Description: "Prefix of the data path.",
diff --git a/pkg/apis/pingcap/v1alpha1/types.go b/pkg/apis/pingcap/v1alpha1/types.go
index 7f92b46dcb7..7839858e308 100644
--- a/pkg/apis/pingcap/v1alpha1/types.go
+++ b/pkg/apis/pingcap/v1alpha1/types.go
@@ -2012,6 +2012,12 @@ type AzblobStorageProvider struct {
// SecretName is the name of secret which stores the
// azblob service account credentials.
SecretName string `json:"secretName,omitempty"`
+ // StorageAccount is the storage account of the azure blob storage
+ // If this field is set, then use this to set backup-manager env
+ // Otherwise retrieve the storage account from secret
+ StorageAccount string `json:"storageAccount,omitempty"`
+ // SasToken is the sas token of the storage account
+ SasToken string `json:"sasToken,omitempty"`
// Prefix of the data path.
Prefix string `json:"prefix,omitempty"`
}
diff --git a/pkg/backup/util/remote.go b/pkg/backup/util/remote.go
index 6125375d32d..fdf0e6026df 100644
--- a/pkg/backup/util/remote.go
+++ b/pkg/backup/util/remote.go
@@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"io"
+ "net/url"
"os"
"path"
"strconv"
@@ -83,10 +84,12 @@ type gcsConfig struct {
}
type azblobConfig struct {
- container string
- accessTier string
- secretName string
- prefix string
+ storageAccount string
+ sasToken string
+ container string
+ accessTier string
+ secretName string
+ prefix string
}
type localConfig struct {
@@ -525,38 +528,46 @@ func newAzblobStorage(conf *azblobConfig) (*blob.Bucket, error) {
// Azure shared key with access to the storage account
accountKey := os.Getenv("AZURE_STORAGE_KEY")
- // check condition for using AAD credentials first
- var usingAAD bool
- if len(clientID) != 0 && len(clientSecret) != 0 && len(tenantID) != 0 {
- usingAAD = true
- } else if len(accountKey) != 0 {
- usingAAD = false
- } else {
- return nil, errors.New("Missing necessary key(s) for credentials")
- }
+ // Azure Storage Account Shared Access Signature Token
+ sasToken := conf.sasToken
- // initialize a new azblob storage using AAD or shared key credentials
var bucket *blob.Bucket
var err error
- if usingAAD {
+ if len(sasToken) != 0 {
+ bucket, err = newAzblobStorageUsingSasToken(conf, account, sasToken)
+ } else if len(clientID) != 0 && len(clientSecret) != 0 && len(tenantID) != 0 {
bucket, err = newAzblobStorageUsingAAD(conf, &azblobAADCred{
account: account,
clientID: clientID,
clientSecret: clientSecret,
tenantID: tenantID,
})
- } else {
+ } else if len(accountKey) != 0 {
bucket, err = newAzblobStorageUsingSharedKey(conf, &azblobSharedKeyCred{
account: account,
sharedKey: accountKey,
})
+ } else {
+ return nil, errors.New("Missing necessary key(s) for credentials")
}
+
if err != nil {
return nil, err
}
return blob.PrefixedBucket(bucket, strings.Trim(conf.prefix, "/")+"/"), nil
}
+func newAzblobStorageUsingSasToken(conf *azblobConfig, account, token string) (*blob.Bucket, error) {
+ // Azure Storage Account.
+ accountName := azureblob.AccountName(account)
+ sasToken := azureblob.SASToken(token)
+ cred := azblob.NewAnonymousCredential()
+ pipeline := azureblob.NewPipeline(cred, azblob.PipelineOptions{})
+ // Create a *blob.Bucket.
+ ctx := context.Background()
+ return azureblob.OpenBucket(ctx, pipeline, accountName, conf.container, &azureblob.Options{SASToken: sasToken})
+}
+
// newAzblobStorageUsingAAD initialize a new azblob storage using AAD credentials
func newAzblobStorageUsingAAD(conf *azblobConfig, cred *azblobAADCred) (*blob.Bucket, error) {
// Azure Storage Account.
@@ -634,6 +645,16 @@ func newGcsStorageOptionForFlag(conf *gcsConfig, flag string) []string {
func newAzblobStorageOptionForFlag(conf *azblobConfig, flag string) []string {
var azblobOptions []string
path := fmt.Sprintf("azure://%s/", path.Join(conf.container, conf.prefix))
+ values := url.Values{}
+ if conf.storageAccount != "" {
+ values.Add("account-name", conf.storageAccount)
+ }
+ if conf.sasToken != "" {
+ values.Add("sas-token", conf.sasToken)
+ }
+ if v := values.Encode(); v != "" {
+ path = path + "?" + v
+ }
if flag != "" && flag != defaultStorageFlag {
// now just set path to special flag
azblobOptions = append(azblobOptions, fmt.Sprintf("--%s=%s", flag, path))
@@ -697,7 +718,10 @@ func makeGcsConfig(gcs *v1alpha1.GcsStorageProvider, fakeRegion bool) *gcsConfig
// makeAzblobConfig constructs azblobConfig parameters
func makeAzblobConfig(azblob *v1alpha1.AzblobStorageProvider) *azblobConfig {
- conf := azblobConfig{}
+ conf := azblobConfig{
+ storageAccount: azblob.StorageAccount,
+ sasToken: azblob.SasToken,
+ }
path := strings.Trim(azblob.Container, "/") + "/" + strings.Trim(azblob.Prefix, "/")
fields := strings.SplitN(path, "/", 2)
diff --git a/pkg/backup/util/util.go b/pkg/backup/util/util.go
index 4aca851e2b5..7fd9f39233e 100644
--- a/pkg/backup/util/util.go
+++ b/pkg/backup/util/util.go
@@ -184,7 +184,7 @@ func generateGcsCertEnvVar(gcs *v1alpha1.GcsStorageProvider) ([]corev1.EnvVar, s
}
// generateAzblobCertEnvVar generate the env info in order to access azure blob storage
-func generateAzblobCertEnvVar(azblob *v1alpha1.AzblobStorageProvider, useAAD bool) ([]corev1.EnvVar, string, error) {
+func generateAzblobCertEnvVar(azblob *v1alpha1.AzblobStorageProvider, secret *corev1.Secret, useSasToken bool) ([]corev1.EnvVar, string, error) {
if len(azblob.AccessTier) == 0 {
azblob.AccessTier = "Cool"
}
@@ -193,64 +193,63 @@ func generateAzblobCertEnvVar(azblob *v1alpha1.AzblobStorageProvider, useAAD boo
Name: "AZURE_ACCESS_TIER",
Value: azblob.AccessTier,
},
+ {
+ Name: "AZURE_STORAGE_ACCOUNT",
+ Value: azblob.StorageAccount,
+ },
}
- if azblob.SecretName != "" {
+ if useSasToken {
+ return envVars, "", nil
+ }
+ _, exist := CheckAllKeysExistInSecret(secret, constants.AzblobClientID, constants.AzblobClientScrt, constants.AzblobTenantID)
+ if exist { // using AAD auth
envVars = append(envVars, []corev1.EnvVar{
{
- Name: "AZURE_STORAGE_ACCOUNT",
+ Name: "AZURE_CLIENT_ID",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
- Key: constants.AzblobAccountName,
+ Key: constants.AzblobClientID,
},
},
},
- }...)
- if useAAD {
- envVars = append(envVars, []corev1.EnvVar{
- {
- Name: "AZURE_CLIENT_ID",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
- Key: constants.AzblobClientID,
- },
- },
- },
- {
- Name: "AZURE_CLIENT_SECRET",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
- Key: constants.AzblobClientScrt,
- },
+ {
+ Name: "AZURE_CLIENT_SECRET",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
+ Key: constants.AzblobClientScrt,
},
},
- {
- Name: "AZURE_TENANT_ID",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
- Key: constants.AzblobTenantID,
- },
+ },
+ {
+ Name: "AZURE_TENANT_ID",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
+ Key: constants.AzblobTenantID,
},
},
- }...)
- } else {
- envVars = append(envVars, []corev1.EnvVar{
- {
- Name: "AZURE_STORAGE_KEY",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
- Key: constants.AzblobAccountKey,
- },
+ },
+ }...)
+ return envVars, "", nil
+ }
+ _, exist = CheckAllKeysExistInSecret(secret, constants.AzblobAccountKey)
+ if exist { // use access key auth
+ envVars = append(envVars, []corev1.EnvVar{
+ {
+ Name: "AZURE_STORAGE_KEY",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: azblob.SecretName},
+ Key: constants.AzblobAccountKey,
},
},
- }...)
- }
+ },
+ }...)
+ return envVars, "", nil
}
- return envVars, "", nil
+ return nil, "azblobKeyOrAADMissing", fmt.Errorf("secret %s/%s missing some keys", secret.Namespace, secret.Name)
}
// GenerateStorageCertEnv generate the env info in order to access backend backup storage
@@ -303,27 +302,25 @@ func GenerateStorageCertEnv(ns string, useKMS bool, provider v1alpha1.StoragePro
return certEnv, reason, err
}
case v1alpha1.BackupStorageTypeAzblob:
- useAAD := true
azblobSecretName := provider.Azblob.SecretName
+ var secret *corev1.Secret
if azblobSecretName != "" {
- secret, err := secretLister.Secrets(ns).Get(azblobSecretName)
+ secret, err = secretLister.Secrets(ns).Get(azblobSecretName)
if err != nil {
err := fmt.Errorf("get azblob secret %s/%s failed, err: %v", ns, azblobSecretName, err)
return certEnv, "GetAzblobSecretFailed", err
}
-
- keyStrAAD, exist := CheckAllKeysExistInSecret(secret, constants.AzblobAccountName, constants.AzblobClientID, constants.AzblobClientScrt, constants.AzblobTenantID)
- if !exist {
- keyStrShared, exist := CheckAllKeysExistInSecret(secret, constants.AzblobAccountName, constants.AzblobAccountKey)
- if !exist {
- err := fmt.Errorf("the azblob secret %s/%s missing some keys for AAD %s or shared %s", ns, azblobSecretName, keyStrAAD, keyStrShared)
- return certEnv, "azblobKeyNotExist", err
- }
- useAAD = false
+ }
+ if provider.Azblob.StorageAccount == "" { // try to get storageAccount from secret
+ account := string(secret.Data[constants.AzblobAccountName])
+ if account == "" {
+ err := fmt.Errorf("secret %s/%s missing some keys, storage account unspecified: %v", ns, azblobSecretName, secret.Data)
+ return certEnv, "azblobAccountNotExist", err
}
+ provider.Azblob.StorageAccount = account
}
-
- certEnv, reason, err = generateAzblobCertEnvVar(provider.Azblob, useAAD)
+ useSasToken := provider.Azblob.SasToken != ""
+ certEnv, reason, err = generateAzblobCertEnvVar(provider.Azblob, secret, useSasToken)
if err != nil {
return certEnv, reason, err
diff --git a/pkg/backup/util/utils_test.go b/pkg/backup/util/utils_test.go
index 7b94ce0c547..37552be011d 100644
--- a/pkg/backup/util/utils_test.go
+++ b/pkg/backup/util/utils_test.go
@@ -168,13 +168,13 @@ func TestGenerateAzblobCertEnvVar(t *testing.T) {
azblob = &v1alpha1.AzblobStorageProvider{
AccessTier: "",
}
- envs, _, err := generateAzblobCertEnvVar(azblob, true)
+ envs, _, err := generateAzblobCertEnvVar(azblob, nil, true)
g.Expect(err).Should(BeNil())
contains(envs, "AZURE_ACCESS_TIER", "Cool")
// test &v1alpha1.AzblobStorageProvider AccessTier set value
azblob.AccessTier = "Hot"
- envs, _, err = generateAzblobCertEnvVar(azblob, true)
+ envs, _, err = generateAzblobCertEnvVar(azblob, nil, true)
g.Expect(err).Should(BeNil())
contains(envs, "AZURE_ACCESS_TIER", "Hot")
}
@@ -275,8 +275,36 @@ func TestGenerateStorageCertEnv(t *testing.T) {
// test azblob secret with key
if test.provider.Azblob != nil && test.provider.Azblob.SecretName != "" {
+ // test using sas token
+ test.provider.Azblob.StorageAccount = "dummy"
+ test.provider.Azblob.SasToken = "dummy"
+ _, _, err := GenerateStorageCertEnv(ns, false, test.provider, informer.Core().V1().Secrets().Lister())
+ g.Expect(err).Should(BeNil())
+
+ // test using sas token, account from env
+ test.provider.Azblob.SasToken = "dummy"
+ s.Data = map[string][]byte{
+ constants.TidbPasswordKey: []byte("dummy"),
+ constants.AzblobAccountName: []byte("dummy"),
+ }
+ err = informer.Core().V1().Secrets().Informer().GetIndexer().Update(s)
+ g.Expect(err).Should(BeNil())
+ _, _, err = GenerateStorageCertEnv(ns, false, test.provider, informer.Core().V1().Secrets().Lister())
+ g.Expect(err).Should(BeNil())
+
+ // test using sas token, missing account
+ test.provider.Azblob.SasToken = "dummy"
+ test.provider.Azblob.StorageAccount = ""
+ s.Data = map[string][]byte{
+ constants.TidbPasswordKey: []byte("dummy"),
+ }
+ err = informer.Core().V1().Secrets().Informer().GetIndexer().Update(s)
+ g.Expect(err).Should(BeNil())
+ _, _, err = GenerateStorageCertEnv(ns, false, test.provider, informer.Core().V1().Secrets().Lister())
+ g.Expect(err.Error()).Should(MatchRegexp(".*storage account unspecified"))
// test missing some critical key
+ test.provider.Azblob.SasToken = ""
s.Data = map[string][]byte{
constants.TidbPasswordKey: []byte("dummy"),
constants.AzblobAccountName: []byte("dummy"),
@@ -289,6 +317,7 @@ func TestGenerateStorageCertEnv(t *testing.T) {
g.Expect(err.Error()).Should(MatchRegexp(".*missing some keys.*"))
// test integrated shared key
+ test.provider.Azblob.SasToken = ""
s.Data = map[string][]byte{
constants.TidbPasswordKey: []byte("dummy"),
constants.AzblobAccountName: []byte("dummy"),