diff --git a/README.md b/README.md index 40a7d77..e6a82c8 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Usage of consul-snapshotter: --azure-blob.container-name string The name of the Azure Blob container to use --azure-blob.container-path string The path to use inside the Azure Blob container --azure-blob.storage-access-key string The Azure Blob storage access key to use + --azure-blob.storage-sas-token The Azure Blob storage SAS token to use instead of an access key --azure-blob.storage-account string The Azure Blob storage account to use --configdir string The path to look for the configuration file (default ".") --consul.lock-key string The Key to use in the KV lock (default "consul-snapshotter/.lock") diff --git a/azure/azure.go b/azure/azure.go index 0fdfd7e..f4cfbf6 100644 --- a/azure/azure.go +++ b/azure/azure.go @@ -7,41 +7,65 @@ import ( "net/url" "os" + "github.com/Azure/azure-pipeline-go/pipeline" "github.com/Azure/azure-storage-blob-go/azblob" ) type config struct { accountName string accountKey string + sasToken string } -func AzureConfig(accountName, accountKey string) (*config, error) { +func AzureConfig(accountName, accountKey, sasToken string) (*config, error) { if accountName == "" { return nil, fmt.Errorf("Azure Account Name not provided") } - if accountKey == "" { - return nil, fmt.Errorf("Azure Account Access Key not provided") + if accountKey == "" && sasToken == "" { + return nil, fmt.Errorf("Azure Account Access Key or SAS Token must be provided") } c := &config{ accountName: accountName, accountKey: accountKey, + sasToken: sasToken, } return c, nil } -func UploadBlob(srcFile, destFile, containerName string, c *config) (int, error) { - +func AuthenticateAccountKey(containerName string, c *config) (pipeline.Pipeline, error) { // Create a default request pipeline using your storage account name and account key credential, err := azblob.NewSharedKeyCredential(c.accountName, c.accountKey) if err != nil { - return 0, fmt.Errorf("Invalid credentials: %s", err) + return nil, fmt.Errorf("Invalid credentials: %s", err) } p := azblob.NewPipeline(credential, azblob.PipelineOptions{}) + return p, err +} + +func AuthenticateSASToken(containerName string, c *config) pipeline.Pipeline { + return azblob.NewPipeline(azblob.NewAnonymousCredential(), azblob.PipelineOptions{}) +} + +func UploadBlob(srcFile, destFile, containerName string, c *config) (int, error) { + var p pipeline.Pipeline + var queryParameters string + + if c.sasToken != "" { + p = AuthenticateSASToken(containerName, c) + queryParameters = "?" + c.sasToken + } else { + var err error + p, err = AuthenticateAccountKey(containerName, c) + if err != nil { + return 0, err + } + } + // TODO: Allow the URL to be a parameter // Setup the blob service URL endpoint URL, _ := url.Parse( - fmt.Sprintf("https://%s.blob.core.windows.net/%s", c.accountName, containerName)) + fmt.Sprintf("https://%s.blob.core.windows.net/%s%s", c.accountName, containerName, queryParameters)) // Create a ContainerURL object using the container URL and a request pipeline containerURL := azblob.NewContainerURL(*URL, p) diff --git a/config-sample.yaml b/config-sample.yaml index d844e73..f9d0649 100644 --- a/config-sample.yaml +++ b/config-sample.yaml @@ -7,7 +7,7 @@ consul: token: "" lock-key: "consul-snapshotter/.lock" lock-timeout: "10m" - + local: destination-path: "/tmp/snapshots" @@ -15,7 +15,8 @@ azure-blob: container-name: "container_name" container-path: "consul/snapshots" storage-account: "azure_account" - # storage-access-key: "" + # storage-access-key: "" # + # storage-sas-token: "" # Use either of these, SAS token takes precedence outputs: - "local" diff --git a/config.go b/config.go index 549bc8b..bfa749a 100644 --- a/config.go +++ b/config.go @@ -28,6 +28,7 @@ type azureOutputConfig struct { ContainerPath string `json:"container-path"` StorageAccount string `json:"azure-storage-account"` StorageAccessKey string `json:"azure-storage-access-key"` + StorageSASToken string `json:"azure-storage-sas-token"` } type config struct { @@ -90,7 +91,8 @@ func (c *config) loadConfig() error { regFlagString("azure-blob.container-name", "", "The name of the Azure Blob container to use") regFlagString("azure-blob.container-path", "", "The path to use inside the Azure Blob container") regFlagString("azure-blob.storage-account", "", "The Azure Blob storage account to use") - regFlagString("azure-blob.storage-access-key", "", "The Azure Blob storage access key to use") + regFlagString("azure-blob.storage-access-key", "", "The Azure Blob storage access key to use (mutually exclusive with azure-blob.storage-sas-token)") + regFlagString("azure-blob.storage-sas-token", "", "The Azure Blob storage SAS token to use (mutually exclusive with azure-blob.storage-access-key)") regFlagString("local.destination-path", viper.GetString("local.destination-path"), "The local path where to save the snapshots") regFlagDuration("local.retention-period", viper.GetDuration("local.retention-period"), "The duration that Local snapshots need to be retained (default \"0s\" - keep forever)") regFlagBoolP("help", "h", false, "Prints this help message") @@ -113,6 +115,7 @@ func (c *config) loadConfig() error { viper.BindEnv("consul.token", "CONSUL_HTTP_TOKEN") viper.BindEnv("azure-blob.storage-account", "AZURE_STORAGE_ACCOUNT") viper.BindEnv("azure-blob.storage-access-key", "AZURE_STORAGE_ACCESS_KEY") + viper.BindEnv("azure-blob.storage-sas-token", "AZURE_STORAGE_SAS_TOKEN") // load config from file viper.SetConfigName("config") @@ -135,6 +138,7 @@ func (c *config) loadConfig() error { azureOutputConfig.ContainerPath = viper.GetString("azure-blob.container-path") azureOutputConfig.StorageAccount = viper.GetString("azure-blob.storage-account") azureOutputConfig.StorageAccessKey = viper.GetString("azure-blob.storage-access-key") + azureOutputConfig.StorageSASToken = viper.GetString("azure-blob.storage-sas-token") // Local output config localOutputConfig := &localOutputConfig{} diff --git a/go.mod b/go.mod index 8c79ccb..48fbc04 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace github.com/ruizink/consul-snapshotter/consul => ./consul replace github.com/ruizink/consul-snapshotter/azure => ./azure require ( + github.com/Azure/azure-pipeline-go v0.2.2 github.com/Azure/azure-storage-blob-go v0.10.0 github.com/hashicorp/consul v1.8.1 github.com/hashicorp/consul/api v1.5.0 diff --git a/main.go b/main.go index 9839ed5..4bfd6bb 100644 --- a/main.go +++ b/main.go @@ -152,6 +152,7 @@ func processOutputs(snap string, c *config) { Filename: outputFileName, StorageAccount: c.AzureOutputConfig.StorageAccount, StorageAccessKey: c.AzureOutputConfig.StorageAccessKey, + StorageSASToken: c.AzureOutputConfig.StorageSASToken, } o.Save(snap) } diff --git a/outputs/azure_blob.go b/outputs/azure_blob.go index ea14db7..9a444ec 100644 --- a/outputs/azure_blob.go +++ b/outputs/azure_blob.go @@ -13,11 +13,12 @@ type AzureBlobOutput struct { Filename string StorageAccount string StorageAccessKey string + StorageSASToken string } func (o *AzureBlobOutput) Save(snap string) { destFile := path.Join(o.ContainerPath, o.Filename) - config, err := azure.AzureConfig(o.StorageAccount, o.StorageAccessKey) + config, err := azure.AzureConfig(o.StorageAccount, o.StorageAccessKey, o.StorageSASToken) if err != nil { log.Println("Invalid Azure config:", err) return