Skip to content

Commit

Permalink
[aztables] adding CosmosDB support for using TokenCredentials (Azure#…
Browse files Browse the repository at this point in the history
…23596)

Just a simple matter of using the proper scope when talking with CosmosDB, which apparently didn't support token auth when this library was first created.

Also includes some work to bring this up "to code", as it were:
* Tests now test against TokenCredentials for both Storage and CosmosDB (previously they only tested the constructor). Bicep file also now has an example of creating a custom role that contains the privileges needed since Cosmos has it's own RBAC.
* Readme and other text all updated to say "Entra ID" instead of AAD

Fixes Azure#21760
  • Loading branch information
richardpark-msft authored Oct 18, 2024
1 parent 1cbed27 commit 6055ff7
Show file tree
Hide file tree
Showing 20 changed files with 456 additions and 326 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
# AzureSDKOwners: @jhendrixMSFT
# ServiceLabel: %Tables
# PRLabel: %Tables
/sdk/data/aztables/ @jhendrixMSFT
/sdk/data/aztables/ @jhendrixMSFT @richardpark-msft

# AzureSDKOwners: @gracewilcox
# ServiceLabel: %KeyVault
Expand Down
11 changes: 10 additions & 1 deletion eng/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@
"Name": "keyvault/azsecrets",
"CoverageGoal": 0.86
},
{
"Name": "data/aztables",
"CoverageGoal": 0.77,
"EnvironmentVariables": {
"TABLES_STORAGE_ACCOUNT_NAME": "fakeaccount",
"TABLES_PRIMARY_STORAGE_ACCOUNT_KEY": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
"TABLES_SHARED_ACCESS_SIGNATURE": "?sig=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
}
},
{
"Name": "data",
"CoverageGoal": 0.78,
Expand Down Expand Up @@ -134,4 +143,4 @@
"CoverageGoal": 0.75
}
]
}
}
3 changes: 2 additions & 1 deletion sdk/data/aztables/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Release History

## 1.2.1 (Unreleased)
## 1.3.0 (Unreleased)

### Features Added
* Client/ServiceClient now supports `azcore.TokenCredential` authentication with Azure Cosmos DB for Table.

### Breaking Changes

Expand Down
11 changes: 7 additions & 4 deletions sdk/data/aztables/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ func main() {
For more information about table service URL's and how to configure custom domain names for Azure Storage check out the [official documentation][azure_portal_account_url]

#### Types of credentials
The clients support different forms of authentication. Cosmos accounts can use a Shared Key Credential, Connection String, or an Shared Access Signature Token for authentication. Storage account can use the same credentials as a Cosmos account and can use the credentials in [`azidentity`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) like `azidentity.NewDefaultAzureCredential()`.

The aztables package supports any of the types that implement the `azcore.TokenCredential` interface, authorization via a Connection String, or authorization with a Shared Access Signature Token.
Both services (Cosmos and Storage) support the the following forms of authentication:
- Microsoft Entra ID token, using one of the collection of types from the [`azidentity`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) module, like [azidentity.DefaultAzureCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-defaultazurecredential). Example [here](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/data/aztables#example-NewServiceClient).
- Shared Key Credential
- Connection String
- Shared Access Signature Token

##### Creating the client with an AAD credential
Use AAD authentication as the credential parameter to authenticate the client:
##### Creating the client with a Microsoft Entra ID credential
Use Microsoft Entra ID authentication as the credential parameter to authenticate the client:
```go
import (
"fmt"
Expand Down
18 changes: 6 additions & 12 deletions sdk/data/aztables/access_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@ import (
)

func TestSetEmptyAccessPolicy(t *testing.T) {
client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.SetAccessPolicy",
}))
defer delete()

_, err := client.SetAccessPolicy(ctx, nil)
require.NoError(t, err)
}

func TestSetAccessPolicy(t *testing.T) {
client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.GetAccessPolicy",
}))
defer delete()

start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
expiration := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
Expand Down Expand Up @@ -72,8 +70,7 @@ func TestSetAccessPolicy(t *testing.T) {
}

func TestSetMultipleAccessPolicies(t *testing.T) {
client, delete := initClientTest(t, "storage", true, tracing.Provider{})
defer delete()
client := initClientTest(t, storageEndpoint, true, tracing.Provider{})

id := "empty"

Expand Down Expand Up @@ -120,11 +117,10 @@ func TestSetMultipleAccessPolicies(t *testing.T) {
}

func TestSetTooManyAccessPolicies(t *testing.T) {
client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.SetAccessPolicy",
Status: tracing.SpanStatusError,
}))
defer delete()

start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
expiration := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
Expand Down Expand Up @@ -155,8 +151,7 @@ func TestSetTooManyAccessPolicies(t *testing.T) {
}

func TestSetNullAccessPolicy(t *testing.T) {
client, delete := initClientTest(t, "storage", true, tracing.Provider{})
defer delete()
client := initClientTest(t, storageEndpoint, true, tracing.Provider{})

id := "null"

Expand All @@ -178,8 +173,7 @@ func TestSetNullAccessPolicy(t *testing.T) {
}

func TestSetInvalidAccessPolicy(t *testing.T) {
client, delete := initClientTest(t, "storage", true, tracing.Provider{})
defer delete()
client := initClientTest(t, storageEndpoint, true, tracing.Provider{})

signedIdentifiers := make([]*SignedIdentifier, 0)
signedIdentifiers = append(signedIdentifiers, &SignedIdentifier{
Expand Down
2 changes: 1 addition & 1 deletion sdk/data/aztables/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go",
"TagPrefix": "go/data/aztables",
"Tag": "go/data/aztables_5a816669aa"
"Tag": "go/data/aztables_7fc6e189d6"
}
52 changes: 21 additions & 31 deletions sdk/data/aztables/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ import (
"github.com/stretchr/testify/require"
)

var services = []string{"storage", "cosmos"}
var services = []endpointType{
storageEndpoint,
cosmosEndpoint,
storageTokenCredentialEndpoint,
cosmosTokenCredentialEndpoint,
}

func TestServiceErrors(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.CreateTable",
Status: tracing.SpanStatusError,
}))
defer delete()

// Create a duplicate table to produce an error
_, err := client.CreateTable(ctx, nil)
Expand All @@ -42,10 +46,9 @@ func TestServiceErrors(t *testing.T) {
func TestCreateTable(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, false, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, false, NewSpanValidator(t, SpanMatcher{
Name: "Client.Delete",
}))
defer delete()

_, err := client.CreateTable(ctx, nil)

Expand All @@ -62,10 +65,9 @@ type mdforAddGet struct {
func TestAddEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.AddEntity",
}))
defer delete()

simpleEntity := createSimpleEntity(1, "partition")

Expand All @@ -85,8 +87,7 @@ func TestAddEntity(t *testing.T) {
func TestAddComplexEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

entity := createComplexEntity(1, "partition")

Expand All @@ -112,10 +113,9 @@ func TestAddComplexEntity(t *testing.T) {
func TestDeleteEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.DeleteEntity",
}))
defer delete()

simpleEntity := createSimpleEntity(1, "partition")

Expand All @@ -132,8 +132,7 @@ func TestDeleteEntity(t *testing.T) {
func TestDeleteEntityWithETag(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

simpleEntity := createSimpleEntity(1, "partition")
simpleEntity2 := createSimpleEntity(2, "partition")
Expand Down Expand Up @@ -168,10 +167,9 @@ func TestDeleteEntityWithETag(t *testing.T) {
func TestMergeEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.GetEntity",
}))
defer delete()

entityToCreate := createSimpleEntity(1, "partition")
marshalled, err := json.Marshal(entityToCreate)
Expand Down Expand Up @@ -230,11 +228,10 @@ func TestMergeEntity(t *testing.T) {
func TestMergeEntityDoesNotExist(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.UpdateEntity",
Status: tracing.SpanStatusError,
}))
defer delete()

entityToCreate := createSimpleEntity(1, "partition")
marshalled, err := json.Marshal(entityToCreate)
Expand All @@ -253,10 +250,9 @@ func TestMergeEntityDoesNotExist(t *testing.T) {
func TestInsertEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Client.UpsertEntity",
}))
defer delete()

// 1. Create Basic Entity
entityToCreate := createSimpleEntityWithRowKey(1, "parti'tion", "one'")
Expand Down Expand Up @@ -322,8 +318,7 @@ func TestInsertEntity(t *testing.T) {
func TestInsertEntityTwice(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

// 1. Create Basic Entity
entityToCreate := createSimpleEntity(1, "partition")
Expand All @@ -347,10 +342,9 @@ type mdForListEntities struct {
func TestQuerySimpleEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{
Name: "Pager[ListEntitiesResponse].NextPage",
}))
defer delete()

// Add 5 entities
entitiesToCreate := createSimpleEntities(5, "partition")
Expand Down Expand Up @@ -407,8 +401,7 @@ func TestQuerySimpleEntity(t *testing.T) {
func TestQueryComplexEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

// Add 5 entities
entitiesToCreate := createComplexEntities(5, "partition")
Expand Down Expand Up @@ -462,8 +455,7 @@ func TestQueryComplexEntity(t *testing.T) {
func TestInvalidEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

badEntity := map[string]any{
"Value": 10,
Expand All @@ -483,8 +475,7 @@ func TestInvalidEntity(t *testing.T) {
func TestContinuationTokens(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

err := insertNEntities("contToken", 10, client)
require.NoError(t, err)
Expand Down Expand Up @@ -524,8 +515,7 @@ func TestContinuationTokens(t *testing.T) {
func TestContinuationTokensFilters(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

err := insertNEntities("contToken", 10, client)
require.NoError(t, err)
Expand Down
12 changes: 4 additions & 8 deletions sdk/data/aztables/entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import (
func TestAddBasicEntity(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

basicEntity := basicTestEntity{
Entity: Entity{
Expand Down Expand Up @@ -65,8 +64,7 @@ func TestAddBasicEntity(t *testing.T) {
func TestEdmMarshalling(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

edmEntity := createEdmEntity(1, "partition")

Expand Down Expand Up @@ -108,8 +106,7 @@ func TestEdmMarshalling(t *testing.T) {
func TestEntityQuotes(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

pk, err := createRandomName(t, "partition")
require.NoError(t, err)
Expand Down Expand Up @@ -165,8 +162,7 @@ func TestEntityQuotes(t *testing.T) {
func TestEntityUnicode(t *testing.T) {
for _, service := range services {
t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) {
client, delete := initClientTest(t, service, true, tracing.Provider{})
defer delete()
client := initClientTest(t, service, true, tracing.Provider{})

pk, err := createRandomName(t, "partition")
require.NoError(t, err)
Expand Down
12 changes: 6 additions & 6 deletions sdk/data/aztables/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/Azure/azure-sdk-for-go/sdk/data/aztables
go 1.18

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0
github.com/stretchr/testify v1.9.0
)
Expand All @@ -17,9 +17,9 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 6055ff7

Please sign in to comment.