diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e29ddec15e..b90af6411e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * [ENHANCEMENT] Update mixin to use new backend metric [#1151](https://github.com/grafana/tempo/pull/1151) (@zalegrala) * [ENHANCEMENT] Make `TempoIngesterFlushesFailing` alert more actionable [#1157](https://github.com/grafana/tempo/pull/1157) (@dannykopping) * [ENHANCEMENT] Switch open-telemetry/opentelemetry-collector to grafana/opentelemetry-collectorl fork, update it to 0.40.0 and add missing dependencies due to the change (@tete17) +* [ENHANCEMENT] Allow environment variables for Azure storage credentials [#1147](https://github.com/grafana/tempo/pull/1147) (@zalegrala) * [BUGFIX] Fix defaults for MaxBytesPerTrace (ingester.max-bytes-per-trace) and MaxSearchBytesPerTrace (ingester.max-search-bytes-per-trace) (@bitprocessor) * [BUGFIX] Ignore empty objects during compaction [#1113](https://github.com/grafana/tempo/pull/1113) (@mdisibio) * [BUGFIX] Add process name to vulture traces to work around display issues [#1127](https://github.com/grafana/tempo/pull/1127) (@mdisibio) diff --git a/docs/tempo/website/configuration/_index.md b/docs/tempo/website/configuration/_index.md index f77c90ca314..b4fddaed512 100644 --- a/docs/tempo/website/configuration/_index.md +++ b/docs/tempo/website/configuration/_index.md @@ -291,6 +291,7 @@ The storage block is used to configure TempoDB. It supports S3, GCS, Azure, loca The following example shows common options. For further platform-specific information refer to the following: * [GCS](gcs/) * [S3](s3/) +* [Azure](azure/) ```yaml # Storage configuration for traces diff --git a/docs/tempo/website/configuration/azure.md b/docs/tempo/website/configuration/azure.md new file mode 100644 index 00000000000..de55e02445f --- /dev/null +++ b/docs/tempo/website/configuration/azure.md @@ -0,0 +1,87 @@ +--- +title: Azure Permissions and Management +weight: 3 +--- + +# Azure Blob Storage Permissions and Management + +In order for Tempo to authenticate to Azure blob storage, the following is +required to access the container. + +* Storage account +* Storage account access key +* Storage account management policy + +The `AZURE_STORAGE_ACCOUNT` and `AZURE_STORAGE_KEY` environment variables can +be used instead of setting the credentials in the Tempo configuration file. + +The following storage account management policy shows an example of cleaning up +files from the container after they have been deleted for a period of time. + +```json +{ + "id": "/subscriptions/00000000-0000-0000000000000000000000/resourceGroups/resourceGroupName/providers/Microsoft.Storage/storageAccounts/accountName/managementPolicies/default", + "lastModifiedTime": "2021-11-30T19:19:54.855455+00:00", + "name": "DefaultManagementPolicy", + "policy": { + "rules": [ + { + "definition": { + "actions": { + "baseBlob": { + "delete": { + "daysAfterLastAccessTimeGreaterThan": null, + "daysAfterModificationGreaterThan": 60.0 + }, + "enableAutoTierToHotFromCool": null, + "tierToArchive": null, + "tierToCool": null + }, + "snapshot": null, + "version": null + }, + "filters": { + "blobIndexMatch": null, + "blobTypes": [ + "blockBlob" + ], + "prefixMatch": [ + "tempo-data" + ] + } + }, + "enabled": true, + "name": "TempoBlobRetention", + "type": "Lifecycle" + }, + { + "definition": { + "actions": { + "baseBlob": null, + "snapshot": null, + "version": { + "delete": { + "daysAfterCreationGreaterThan": 7.0 + }, + "tierToArchive": null, + "tierToCool": null + } + }, + "filters": { + "blobIndexMatch": null, + "blobTypes": [ + "blockBlob" + ], + "prefixMatch": [] + } + }, + "enabled": true, + "name": "VersionRetention", + "type": "Lifecycle" + } + ] + }, + "resourceGroup": "resource-group-name", + "type": "Microsoft.Storage/storageAccounts/managementPolicies" +} +``` diff --git a/tempodb/backend/azure/azure_helpers.go b/tempodb/backend/azure/azure_helpers.go index fc0016f1e76..c10dd2adf63 100644 --- a/tempodb/backend/azure/azure_helpers.go +++ b/tempodb/backend/azure/azure_helpers.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "os" "strings" "time" @@ -20,7 +21,18 @@ const ( ) func GetContainerURL(ctx context.Context, cfg *Config, hedge bool) (blob.ContainerURL, error) { - c, err := blob.NewSharedKeyCredential(cfg.StorageAccountName.String(), cfg.StorageAccountKey.String()) + accountName := cfg.StorageAccountName.String() + accountKey := cfg.StorageAccountKey.String() + + if accountName == "" { + accountName = os.Getenv("AZURE_STORAGE_ACCOUNT") + } + + if accountKey == "" { + accountKey = os.Getenv("AZURE_STORAGE_KEY") + } + + c, err := blob.NewSharedKeyCredential(accountName, accountKey) if err != nil { return blob.ContainerURL{}, err } @@ -63,12 +75,12 @@ func GetContainerURL(ctx context.Context, cfg *Config, hedge bool) (blob.Contain HTTPSender: httpSender, }) - u, err := url.Parse(fmt.Sprintf("https://%s.%s", cfg.StorageAccountName, cfg.Endpoint)) + u, err := url.Parse(fmt.Sprintf("https://%s.%s", accountName, cfg.Endpoint)) // If the endpoint doesn't start with blob.core we can assume Azurite is being used // So the endpoint should follow Azurite URL style if !strings.HasPrefix(cfg.Endpoint, "blob.core") { - u, err = url.Parse(fmt.Sprintf("http://%s/%s", cfg.Endpoint, cfg.StorageAccountName)) + u, err = url.Parse(fmt.Sprintf("http://%s/%s", cfg.Endpoint, accountName)) } if err != nil { diff --git a/tempodb/backend/azure/azure_test.go b/tempodb/backend/azure/azure_test.go index 72362db5346..c6bae8b82bb 100644 --- a/tempodb/backend/azure/azure_test.go +++ b/tempodb/backend/azure/azure_test.go @@ -5,16 +5,37 @@ import ( "context" "net/http" "net/http/httptest" + "os" "sync/atomic" "testing" "time" "github.com/google/uuid" + "github.com/grafana/dskit/flagext" "github.com/grafana/tempo/tempodb/backend" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestCredentials(t *testing.T) { + _, _, _, err := New(&Config{}) + require.Error(t, err) + + os.Setenv("AZURE_STORAGE_ACCOUNT", "testing") + os.Setenv("AZURE_STORAGE_KEY", "dGVzdGluZwo=") + + defer os.Unsetenv("AZURE_STORAGE_ACCOUNT") + defer os.Unsetenv("AZURE_STORAGE_KEY") + + count := int32(0) + server := fakeServer(t, 1*time.Second, &count) + + _, _, _, err = New(&Config{ + Endpoint: server.URL[7:], // [7:] -> strip http://, + }) + require.NoError(t, err) +} + func TestHedge(t *testing.T) { tests := []struct { name string @@ -45,12 +66,14 @@ func TestHedge(t *testing.T) { server := fakeServer(t, tc.returnIn, &count) r, w, _, err := New(&Config{ - MaxBuffers: 3, - BufferSize: 1000, - ContainerName: "blerg", - Endpoint: server.URL[7:], // [7:] -> strip http://, - HedgeRequestsAt: tc.hedgeAt, - HedgeRequestsUpTo: 2, + StorageAccountName: flagext.Secret{Value: "testing"}, + StorageAccountKey: flagext.Secret{Value: "YQo="}, + MaxBuffers: 3, + BufferSize: 1000, + ContainerName: "blerg", + Endpoint: server.URL[7:], // [7:] -> strip http://, + HedgeRequestsAt: tc.hedgeAt, + HedgeRequestsUpTo: 2, }) require.NoError(t, err)