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:
Cory LaNou
2026-01-11 10:54:48 -06:00
committed by GitHub
parent 36047f4dfe
commit c77ecef421
4 changed files with 45 additions and 3 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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