mirror of
https://github.com/benbjohnson/litestream.git
synced 2026-01-24 20:56:48 +00:00
feat(abs): add SAS token authentication for Azure Blob Storage (#983)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ type ReplicaClient struct {
|
||||
// Azure credentials
|
||||
AccountName string
|
||||
AccountKey string
|
||||
SASToken string // SAS token for container-level access
|
||||
Endpoint string
|
||||
|
||||
// Azure Blob Storage container information
|
||||
@@ -137,6 +138,12 @@ func (c *ReplicaClient) Init(ctx context.Context) (err error) {
|
||||
},
|
||||
}
|
||||
|
||||
// Check for SAS token first (highest priority for explicit credentials)
|
||||
sasToken := c.SASToken
|
||||
if sasToken == "" {
|
||||
sasToken = os.Getenv("LITESTREAM_AZURE_SAS_TOKEN")
|
||||
}
|
||||
|
||||
// Check if we have explicit credentials or should use default credential chain
|
||||
accountKey := c.AccountKey
|
||||
if accountKey == "" {
|
||||
@@ -144,8 +151,22 @@ func (c *ReplicaClient) Init(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
// Create Azure Blob Storage client with appropriate authentication
|
||||
// Priority: SAS token > Shared key > Default credential chain
|
||||
var client *azblob.Client
|
||||
if accountKey != "" && c.AccountName != "" {
|
||||
if sasToken != "" {
|
||||
// SAS token authentication - append token to endpoint URL
|
||||
if accountKey != "" {
|
||||
slog.Warn("both SAS token and account key configured, using SAS token")
|
||||
} else {
|
||||
slog.Debug("using SAS token authentication")
|
||||
}
|
||||
// Strip leading "?" if present to avoid double "?"
|
||||
endpointWithSAS := fmt.Sprintf("%s?%s", endpoint, strings.TrimPrefix(sasToken, "?"))
|
||||
client, err = azblob.NewClientWithNoCredential(endpointWithSAS, clientOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("abs: cannot create azure blob client with SAS token: %w", err)
|
||||
}
|
||||
} else if accountKey != "" && c.AccountName != "" {
|
||||
// Use shared key authentication (existing behavior)
|
||||
slog.Debug("using shared key authentication")
|
||||
credential, err := azblob.NewSharedKeyCredential(c.AccountName, accountKey)
|
||||
|
||||
@@ -952,6 +952,7 @@ type ReplicaSettings struct {
|
||||
// ABS settings
|
||||
AccountName string `yaml:"account-name"`
|
||||
AccountKey string `yaml:"account-key"`
|
||||
SASToken string `yaml:"sas-token"`
|
||||
|
||||
// SFTP settings
|
||||
Host string `yaml:"host"`
|
||||
@@ -1058,6 +1059,9 @@ func (rs *ReplicaSettings) SetDefaults(src *ReplicaSettings) {
|
||||
if rs.AccountKey == "" {
|
||||
rs.AccountKey = src.AccountKey
|
||||
}
|
||||
if rs.SASToken == "" {
|
||||
rs.SASToken = src.SASToken
|
||||
}
|
||||
|
||||
// SFTP settings
|
||||
if rs.Host == "" {
|
||||
@@ -1494,6 +1498,7 @@ func newABSReplicaClientFromConfig(c *ReplicaConfig, _ *litestream.Replica) (_ *
|
||||
client := abs.NewReplicaClient()
|
||||
client.AccountName = c.AccountName
|
||||
client.AccountKey = c.AccountKey
|
||||
client.SASToken = c.SASToken
|
||||
client.Bucket = c.Bucket
|
||||
client.Path = c.Path
|
||||
client.Endpoint = c.Endpoint
|
||||
|
||||
@@ -232,10 +232,24 @@ replicas:
|
||||
account-key: your-account-key
|
||||
```
|
||||
|
||||
**Using SAS Token** (for granular container-level access):
|
||||
|
||||
```yaml
|
||||
replicas:
|
||||
- url: abs://container-name/path
|
||||
account-name: your-account-name
|
||||
sas-token: "sv=2023-01-03&ss=b&srt=co&sp=rwdlacx..."
|
||||
```
|
||||
|
||||
Or via environment variable: `LITESTREAM_AZURE_SAS_TOKEN`
|
||||
|
||||
**Alternative Authentication**:
|
||||
|
||||
- Connection string: `AZURE_STORAGE_CONNECTION_STRING`
|
||||
- Managed identity on Azure
|
||||
- SAS token: `sas-token` config or `LITESTREAM_AZURE_SAS_TOKEN` env var
|
||||
- Account key: `account-key` config or `LITESTREAM_AZURE_ACCOUNT_KEY` env var
|
||||
- Managed identity on Azure (via DefaultAzureCredential)
|
||||
|
||||
**Authentication Priority**: SAS token > Account key > Default credential chain
|
||||
|
||||
## Alibaba Cloud OSS
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ var (
|
||||
var (
|
||||
absAccountName = flag.String("abs-account-name", os.Getenv("LITESTREAM_ABS_ACCOUNT_NAME"), "")
|
||||
absAccountKey = flag.String("abs-account-key", os.Getenv("LITESTREAM_ABS_ACCOUNT_KEY"), "")
|
||||
absSASToken = flag.String("abs-sas-token", os.Getenv("LITESTREAM_ABS_SAS_TOKEN"), "")
|
||||
absBucket = flag.String("abs-bucket", os.Getenv("LITESTREAM_ABS_BUCKET"), "")
|
||||
absPath = flag.String("abs-path", os.Getenv("LITESTREAM_ABS_PATH"), "")
|
||||
)
|
||||
@@ -390,6 +391,7 @@ func NewABSReplicaClient(tb testing.TB) *abs.ReplicaClient {
|
||||
c := abs.NewReplicaClient()
|
||||
c.AccountName = *absAccountName
|
||||
c.AccountKey = *absAccountKey
|
||||
c.SASToken = *absSASToken
|
||||
c.Bucket = *absBucket
|
||||
c.Path = path.Join(*absPath, fmt.Sprintf("%016x", rand.Uint64()))
|
||||
return c
|
||||
|
||||
Reference in New Issue
Block a user