diff --git a/README.md b/README.md index f485829..f8607a1 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ import ( "os" "strings" - "github.com/redis-developer/go-redis-entraid/entraid" + "github.com/redis/go-redis-entraid/entraid" "github.com/redis/go-redis/v9" ) @@ -97,7 +97,7 @@ export AZURE_AUTHORITY_HOST="https://login.microsoftonline.com" # For custom au ### Running the Example ```bash go mod init your-app -go get github.com/redis-developer/go-redis-entraid +go get github.com/redis/go-redis-entraid go run main.go ``` @@ -411,10 +411,10 @@ authority := identity.AuthorityConfiguration{ ```go // Create provider for system assigned identity provider, err := entraid.NewManagedIdentityCredentialsProvider(entraid.ManagedIdentityCredentialsProviderOptions{ - CredentialsProviderOptions: entraid.CredentialsProviderOptions{ - ClientID: os.Getenv("AZURE_CLIENT_ID"), + ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{ + ManagedIdentityType: identity.SystemAssignedIdentity, + Scopes: []string{"https://redis.azure.com/.default"}, }, - ManagedIdentityType: identity.SystemAssignedIdentity, }) ``` @@ -425,23 +425,27 @@ provider, err := entraid.NewManagedIdentityCredentialsProvider(entraid.ManagedId CredentialsProviderOptions: entraid.CredentialsProviderOptions{ ClientID: os.Getenv("AZURE_CLIENT_ID"), }, - ManagedIdentityType: identity.UserAssignedIdentity, - UserAssignedClientID: os.Getenv("USER_ASSIGNED_CLIENT_ID"), + ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{ + ManagedIdentityType: identity.UserAssignedIdentity, + UserAssignedClientID: os.Getenv("AZURE_USER_ASSIGNED_MANAGED_ID"), + Scopes: []string{"https://redis.azure.com/.default"}, + }, }) ``` ### Client Secret Authentication ```go // Create provider for client secret authentication -provider, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialIdentityProviderOptions{ - CredentialsProviderOptions: entraid.CredentialsProviderOptions{ +provider, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialCredentialsProviderOptions{ + ConfidentialIdentityProviderOptions: identity.ConfidentialIdentityProviderOptions{ ClientID: os.Getenv("AZURE_CLIENT_ID"), - }, - CredentialsType: identity.ClientSecretCredentialType, - ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), - Authority: identity.AuthorityConfiguration{ - AuthorityType: identity.AuthorityTypeDefault, - TenantID: os.Getenv("AZURE_TENANT_ID"), + ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), + CredentialsType: identity.ClientSecretCredentialType, + Authority: identity.AuthorityConfiguration{ + AuthorityType: identity.AuthorityTypeMultiTenant, + TenantID: os.Getenv("AZURE_TENANT_ID"), + }, + Scopes: []string{"https://redis.azure.com/.default"}, }, }) ``` @@ -454,16 +458,27 @@ if err != nil { log.Fatal(err) } -provider, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialIdentityProviderOptions{ - CredentialsProviderOptions: entraid.CredentialsProviderOptions{ +provider, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialCredentialsProviderOptions{ + ConfidentialIdentityProviderOptions: identity.ConfidentialIdentityProviderOptions{ ClientID: os.Getenv("AZURE_CLIENT_ID"), + CredentialsType: identity.ClientCertificateCredentialType, + Authority: identity.AuthorityConfiguration{ + AuthorityType: identity.AuthorityTypeMultiTenant, + TenantID: os.Getenv("AZURE_TENANT_ID"), + }, + Scopes: []string{"https://redis.azure.com/.default"}, + ClientCert: []*x509.Certificate{cert.Leaf}, + ClientPrivateKey: cert.PrivateKey, }, - CredentialsType: identity.ClientCertificateCredentialType, - ClientCert: []*x509.Certificate{cert.Leaf}, - ClientPrivateKey: cert.PrivateKey, - Authority: identity.AuthorityConfiguration{ - AuthorityType: identity.AuthorityTypeDefault, - TenantID: os.Getenv("AZURE_TENANT_ID"), +}) +``` + +### Default Azure Identity +```go +// Create a default credentials provider +provider, err := entraid.NewDefaultAzureCredentialsProvider(entraid.DefaultAzureCredentialsProviderOptions{ + DefaultAzureIdentityProviderOptions: identity.DefaultAzureIdentityProviderOptions{ + Scopes: []string{"https://redis.azure.com/.default"}, }, }) ``` @@ -483,10 +498,10 @@ import ( "strings" "time" - "github.com/redis-developer/go-redis-entraid/entraid" - "github.com/redis-developer/go-redis-entraid/entraid/identity" - "github.com/redis-developer/go-redis-entraid/entraid/manager" - "github.com/redis-developer/go-redis-entraid/entraid/shared" + "github.com/redis/go-redis-entraid/entraid" + "github.com/redis/go-redis-entraid/entraid/identity" + "github.com/redis/go-redis-entraid/entraid/manager" + "github.com/redis/go-redis-entraid/entraid/shared" "github.com/redis/go-redis/v9" ) @@ -855,7 +870,7 @@ The library provides several error types that you can check against: ```go // Import the shared package to access error types -import "github.com/redis-developer/go-redis-entraid/shared" +import "github.com/redis/go-redis-entraid/shared" // Available error types: var ( diff --git a/credentials_provider.go b/credentials_provider.go index 0cd86bc..34839b5 100644 --- a/credentials_provider.go +++ b/credentials_provider.go @@ -7,8 +7,8 @@ import ( "fmt" "sync" - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/token" "github.com/redis/go-redis/v9/auth" ) diff --git a/credentials_provider_test.go b/credentials_provider_test.go index d9bfb3f..a63826c 100644 --- a/credentials_provider_test.go +++ b/credentials_provider_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - "github.com/redis-developer/go-redis-entraid/identity" - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" "github.com/redis/go-redis/v9/auth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/entraid.go b/entraid.go index 06896c5..6eb0625 100644 --- a/entraid.go +++ b/entraid.go @@ -1,6 +1,6 @@ package entraid -import "github.com/redis-developer/go-redis-entraid/shared" +import "github.com/redis/go-redis-entraid/shared" // IdentityProvider is an alias for the shared.IdentityProvider interface. type IdentityProvider = shared.IdentityProvider diff --git a/entraid_test.go b/entraid_test.go index a824e29..88a3127 100644 --- a/entraid_test.go +++ b/entraid_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" "github.com/redis/go-redis/v9/auth" "github.com/stretchr/testify/mock" ) diff --git a/examples/custom_idp/go.mod b/examples/custom_idp/go.mod new file mode 100644 index 0000000..32369ca --- /dev/null +++ b/examples/custom_idp/go.mod @@ -0,0 +1,25 @@ +module custom_example + +go 1.23.4 + +require ( + github.com/redis/go-redis-entraid v0.0.0-20250415111332-9d087bc29c12 + github.com/redis/go-redis/v9 v9.5.3-0.20250415103233-40a89c56cc52 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/examples/custom_idp/go.sum b/examples/custom_idp/go.sum new file mode 100644 index 0000000..0ee2c1d --- /dev/null +++ b/examples/custom_idp/go.sum @@ -0,0 +1,53 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis-entraid v0.0.0-20250415111332-9d087bc29c12 h1:H5ZfgueBAxs2eAvXtCMEbT2/fLQz/wxW5Ds4c0uzl50= +github.com/redis/go-redis-entraid v0.0.0-20250415111332-9d087bc29c12/go.mod h1:uXKLxCMUAu1VKgWdt8gWc4PWCygiL2pAI5XpnRSVc0w= +github.com/redis/go-redis/v9 v9.5.3-0.20250415103233-40a89c56cc52 h1:jRx2gINoJsGKxi/RYXCq1VneAAYes9JxUp13xH2oU2g= +github.com/redis/go-redis/v9 v9.5.3-0.20250415103233-40a89c56cc52/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/custom_idp/main.go b/examples/custom_idp/main.go new file mode 100644 index 0000000..e4e4f14 --- /dev/null +++ b/examples/custom_idp/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" + redis "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + idp := NewFakeIdentityProvider("local", "pass") + parser := &fakeIdentityProviderResponseParser{} + // create token manager + + tm, err := manager.NewTokenManager(idp, manager.TokenManagerOptions{ + IdentityProviderResponseParser: parser, + }) + + cp, err := entraid.NewCredentialsProvider(tm, entraid.CredentialsProviderOptions{}) + if err != nil { + panic(err) + } + + client := redis.NewClient(&redis.Options{ + Addr: ":6379", + StreamingCredentialsProvider: cp, + }) + + ok, err := client.Ping(ctx).Result() + if err != nil { + panic(err) + } + fmt.Println("Ping result:", ok) +} + +var _ entraid.IdentityProvider = (*FakeIdentityProvider)(nil) + +type FakeIdentityProvider struct { + username string + password string +} + +// RequestToken simulates a request to an identity provider and returns a fake token. +// In a real implementation, this would involve making a network request to the identity provider. +func (f *FakeIdentityProvider) RequestToken() (entraid.IdentityProviderResponse, error) { + // Simulate a successful token request + return shared.NewIDPResponse( + shared.ResponseTypeRawToken, + fmt.Sprintf("%s:%s:%d", f.username, f.password, time.Now().Add(1*time.Hour).Unix()), + ) +} + +// NewFakeIdentityProvider creates a new instance of FakeIdentityProvider with the given username and password. +func NewFakeIdentityProvider(username, password string) *FakeIdentityProvider { + return &FakeIdentityProvider{ + username: username, + password: password, + } +} + +type fakeIdentityProviderResponseParser struct { +} + +// ParseResponse simulates the parsing of a response from an identity provider. +func (f *fakeIdentityProviderResponseParser) ParseResponse(response entraid.IdentityProviderResponse) (*token.Token, error) { + if response.Type() == shared.ResponseTypeRawToken { + rawToken := response.RawToken() + username, password := "", "" + var expiresOnUnix int64 + + // parse the raw token string + // assuming the format is "username:password:expiresOnUnix" + // where expiresOnUnix is a unix timestamp + parts := strings.Split(rawToken, ":") + if len(parts) != 3 { + return nil, fmt.Errorf("invalid raw token format") + } + username = parts[0] + password = parts[1] + expiresOnUnix, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse raw token: %w", err) + } + + // convert the unix timestamp to time.Time + expiresOn := time.Unix(expiresOnUnix, 0) + now := time.Now() + return token.New(username, password, rawToken, expiresOn, now, int64(expiresOn.Sub(now).Seconds())), nil + } + return nil, fmt.Errorf("unsupported response type: %s", response.Type()) +} + +var _ entraid.IdentityProviderResponseParser = (*fakeIdentityProviderResponseParser)(nil) diff --git a/examples/default_azure_identity/main.go b/examples/default_azure_identity/main.go new file mode 100644 index 0000000..da21373 --- /dev/null +++ b/examples/default_azure_identity/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "fmt" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + redis "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Create a default azure identity credentials provider + // This example uses the default Azure identity chain + cp, err := entraid.NewDefaultAzureCredentialsProvider(entraid.DefaultAzureCredentialsProviderOptions{ + DefaultAzureIdentityProviderOptions: identity.DefaultAzureIdentityProviderOptions{ + Scopes: []string{"https://redis.azure.com/.default"}, + }, + }) + if err != nil { + panic(fmt.Errorf("failed to create default azure identity credentials provider: %w", err)) + } + + // Create Redis client with the credentials provider + redisClient := redis.NewClient(&redis.Options{ + Addr: "your-redis-host:6379", + StreamingCredentialsProvider: cp, + }) + + // Test the connection + ok, err := redisClient.Ping(ctx).Result() + if err != nil { + panic(fmt.Errorf("failed to ping Redis: %w", err)) + } + fmt.Println("Ping result:", ok) +} diff --git a/examples/entraid/clientcert/go.mod b/examples/entraid/clientcert/go.mod new file mode 100644 index 0000000..0e049a9 --- /dev/null +++ b/examples/entraid/clientcert/go.mod @@ -0,0 +1,32 @@ +module clientcert + +go 1.23.4 + +require ( + config v0.0.0 + github.com/redis/go-redis-entraid v0.0.0-20250519132904-1e25b29e9a07 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) + +replace ( + config => ../config + github.com/redis/go-redis-entraid => ../../../ + github.com/redis/go-redis/v9 => github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) diff --git a/examples/entraid/clientcert/go.sum b/examples/entraid/clientcert/go.sum new file mode 100644 index 0000000..915d56f --- /dev/null +++ b/examples/entraid/clientcert/go.sum @@ -0,0 +1,51 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/entraid/clientcert/main.go b/examples/entraid/clientcert/main.go new file mode 100644 index 0000000..e2dd1d5 --- /dev/null +++ b/examples/entraid/clientcert/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "log" + "os" + + "config" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Load configuration + cfg, err := config.LoadConfig(os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Create a confidential identity credentials provider with certificate authentication + cp, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialCredentialsProviderOptions{ + ConfidentialIdentityProviderOptions: identity.ConfidentialIdentityProviderOptions{ + ClientID: cfg.AzureClientID, + ClientSecret: cfg.AzureClientSecret, + CredentialsType: identity.ClientCertificateCredentialType, + Authority: identity.AuthorityConfiguration{ + AuthorityType: identity.AuthorityTypeMultiTenant, + TenantID: cfg.AzureTenantID, + }, + Scopes: cfg.GetRedisScopes(), + ClientCert: parseCertificates(cfg.AzureCert), + ClientPrivateKey: []byte(cfg.AzurePrivateKey), + }, + }) + if err != nil { + log.Fatalf("Failed to create credentials provider: %v", err) + } + + // Create Redis client with streaming credentials provider + opts, err := redis.ParseURL(cfg.Endpoints["standalone-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + opts.StreamingCredentialsProvider = cp + redisClient := redis.NewClient(opts) + + // Create second Redis client for cluster + clusterOpts, err := redis.ParseURL(cfg.Endpoints["cluster-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + clusterClient := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{clusterOpts.Addr}, + StreamingCredentialsProvider: cp, + }) + + // Test the connection + pong, err := redisClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis: %v", err) + } + fmt.Printf("Successfully connected to Redis standalone: %s\n", pong) + + // Test cluster connection + clusterPong, err := clusterClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis cluster: %v", err) + } + fmt.Printf("Successfully connected to Redis cluster: %s\n", clusterPong) + + // Set a test key + err = redisClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key: %v", err) + } + + // Get the test key + val, err := redisClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key: %v", err) + } + fmt.Printf("Retrieved value from standalone: %s\n", val) + + // Set a test key in cluster + err = clusterClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key in cluster: %v", err) + } + + // Get the test key from cluster + clusterVal, err := clusterClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key from cluster: %v", err) + } + fmt.Printf("Retrieved value from cluster: %s\n", clusterVal) +} + +func parseCertificates(pemData string) []*x509.Certificate { + var certs []*x509.Certificate + for { + block, rest := pem.Decode([]byte(pemData)) + if block == nil { + break + } + if block.Type == "CERTIFICATE" { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + log.Fatalf("Failed to parse certificate: %v", err) + } + certs = append(certs, cert) + } + pemData = string(rest) + } + return certs +} diff --git a/examples/entraid/clientsecret/go.mod b/examples/entraid/clientsecret/go.mod new file mode 100644 index 0000000..7b21a46 --- /dev/null +++ b/examples/entraid/clientsecret/go.mod @@ -0,0 +1,32 @@ +module clientsecret + +go 1.23.4 + +require ( + config v0.0.0 + github.com/redis/go-redis-entraid v0.0.0-20250519132904-1e25b29e9a07 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +replace ( + config => ../config + github.com/redis/go-redis-entraid => ../../../ + github.com/redis/go-redis/v9 => github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/examples/entraid/clientsecret/go.sum b/examples/entraid/clientsecret/go.sum new file mode 100644 index 0000000..915d56f --- /dev/null +++ b/examples/entraid/clientsecret/go.sum @@ -0,0 +1,51 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/entraid/clientsecret/main.go b/examples/entraid/clientsecret/main.go new file mode 100644 index 0000000..de06f24 --- /dev/null +++ b/examples/entraid/clientsecret/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "config" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Load configuration + cfg, err := config.LoadConfig(os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Create a confidential identity credentials provider + // This example uses client secret authentication + cp, err := entraid.NewConfidentialCredentialsProvider(entraid.ConfidentialCredentialsProviderOptions{ + ConfidentialIdentityProviderOptions: identity.ConfidentialIdentityProviderOptions{ + ClientID: cfg.AzureClientID, + ClientSecret: cfg.AzureClientSecret, + CredentialsType: identity.ClientSecretCredentialType, + Authority: identity.AuthorityConfiguration{ + AuthorityType: identity.AuthorityTypeMultiTenant, + TenantID: cfg.AzureTenantID, + }, + Scopes: cfg.GetRedisScopes(), + }, + }) + if err != nil { + log.Fatalf("Failed to create credentials provider: %v", err) + } + + // Create Redis client with streaming credentials provider + opts, err := redis.ParseURL(cfg.Endpoints["standalone-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + opts.StreamingCredentialsProvider = cp + redisClient := redis.NewClient(opts) + + // Create second Redis client for cluster + clusterOpts, err := redis.ParseURL(cfg.Endpoints["cluster-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + clusterClient := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{clusterOpts.Addr}, + StreamingCredentialsProvider: cp, + }) + + // Test the connection + pong, err := redisClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis: %v", err) + } + fmt.Printf("Successfully connected to Redis standalone: %s\n", pong) + + // Test cluster connection + clusterPong, err := clusterClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis cluster: %v", err) + } + fmt.Printf("Successfully connected to Redis cluster: %s\n", clusterPong) + + // Set a test key + err = redisClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key: %v", err) + } + + // Get the test key + val, err := redisClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key: %v", err) + } + fmt.Printf("Retrieved value from standalone: %s\n", val) + + // Set a test key in cluster + err = clusterClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key in cluster: %v", err) + } + + // Get the test key from cluster + clusterVal, err := clusterClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key from cluster: %v", err) + } + fmt.Printf("Retrieved value from cluster: %s\n", clusterVal) +} diff --git a/examples/entraid/config/config.go b/examples/entraid/config/config.go new file mode 100644 index 0000000..863f6b0 --- /dev/null +++ b/examples/entraid/config/config.go @@ -0,0 +1,95 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + "strings" +) + +// RedisEndpoint represents a Redis endpoint configuration +// It is loaded from a JSON file +type RedisEndpoint struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + TLS bool `json:"tls"` + CertificatesLocation string `json:"certificatesLocation,omitempty"` + Endpoints []string `json:"endpoints"` +} + +// EntraidConfig represents the configuration for Entra ID authentication +// It is loaded from a both JSON file and environment variables +type EntraidConfig struct { + // JSON config fields + Endpoints map[string]RedisEndpoint `json:"endpoints"` + + // Azure environment variables + AzureClientID string `json:"-"` + AzureClientSecret string `json:"-"` + AzureTenantID string `json:"-"` + AzureAuthority string `json:"-"` + AzureRedisScopes string `json:"-"` + AzureCert string `json:"-"` + AzurePrivateKey string `json:"-"` + AzureUserAssignedManagedID string `json:"-"` +} + +// LoadConfig loads the configuration from both JSON file and environment variables +func LoadConfig(configPath string) (*EntraidConfig, error) { + config := &EntraidConfig{} + + // Load from JSON file first + if configPath == "" { + configPath = "../endpoints.json" // Default path if not set + } + + file, err := os.Open(configPath) + if err != nil { + file, err = os.Open("endpoints.json") + if err != nil { + return nil, fmt.Errorf("failed to open configuration file: %v", err) + } + } + + defer file.Close() + decoder := json.NewDecoder(file) + err = decoder.Decode(&config.Endpoints) + if err != nil { + return nil, fmt.Errorf("failed to decode configuration file: %v", err) + } + // Override with environment variables if they exist + if envClientID := os.Getenv("AZURE_CLIENT_ID"); envClientID != "" { + config.AzureClientID = envClientID + } + if envClientSecret := os.Getenv("AZURE_CLIENT_SECRET"); envClientSecret != "" { + config.AzureClientSecret = envClientSecret + } + if envTenantID := os.Getenv("AZURE_TENANT_ID"); envTenantID != "" { + config.AzureTenantID = envTenantID + } + if envAuthority := os.Getenv("AZURE_AUTHORITY"); envAuthority != "" { + config.AzureAuthority = envAuthority + } + if envRedisScopes := os.Getenv("AZURE_REDIS_SCOPES"); envRedisScopes != "" { + config.AzureRedisScopes = envRedisScopes + } + if envCert := os.Getenv("AZURE_CERT"); envCert != "" { + config.AzureCert = envCert + } + if envPrivateKey := os.Getenv("AZURE_PRIVATE_KEY"); envPrivateKey != "" { + config.AzurePrivateKey = envPrivateKey + } + if envManagedID := os.Getenv("AZURE_USER_ASSIGNED_MANAGED_ID"); envManagedID != "" { + config.AzureUserAssignedManagedID = envManagedID + } + + return config, nil +} + +// GetRedisScopes returns the Redis scopes as a string slice +func (c *EntraidConfig) GetRedisScopes() []string { + if c.AzureRedisScopes == "" { + return []string{"https://redis.azure.com/.default"} // Default scope + } + return strings.Split(c.AzureRedisScopes, ",") +} diff --git a/examples/entraid/config/go.mod b/examples/entraid/config/go.mod new file mode 100644 index 0000000..b62db2e --- /dev/null +++ b/examples/entraid/config/go.mod @@ -0,0 +1,3 @@ +module config + +go 1.23.4 \ No newline at end of file diff --git a/examples/entraid/defaultcredentials/go.mod b/examples/entraid/defaultcredentials/go.mod new file mode 100644 index 0000000..829b73e --- /dev/null +++ b/examples/entraid/defaultcredentials/go.mod @@ -0,0 +1,32 @@ +module defaultcredentials + +go 1.23.4 + +require ( + config v0.0.0 + github.com/redis/go-redis-entraid v0.0.0-00010101000000-000000000000 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) + +replace ( + config => ../config + github.com/redis/go-redis-entraid => ../../../ + github.com/redis/go-redis/v9 => github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) diff --git a/examples/entraid/defaultcredentials/go.sum b/examples/entraid/defaultcredentials/go.sum new file mode 100644 index 0000000..915d56f --- /dev/null +++ b/examples/entraid/defaultcredentials/go.sum @@ -0,0 +1,51 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/entraid/defaultcredentials/main.go b/examples/entraid/defaultcredentials/main.go new file mode 100644 index 0000000..a2f80c7 --- /dev/null +++ b/examples/entraid/defaultcredentials/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "config" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Load configuration + cfg, err := config.LoadConfig(os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Create a default credentials provider + // This will try different authentication methods in sequence: + // 1. Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, etc.) + // 2. Managed Identity (system-assigned or user-assigned) + // 3. Azure CLI credentials + // 4. Visual Studio Code credentials + cp, err := entraid.NewDefaultAzureCredentialsProvider(entraid.DefaultAzureCredentialsProviderOptions{ + DefaultAzureIdentityProviderOptions: identity.DefaultAzureIdentityProviderOptions{ + Scopes: []string{"https://redis.azure.com/.default"}, + }, + }) + if err != nil { + log.Fatalf("Failed to create credentials provider: %v", err) + } + + // Create Redis client with streaming credentials provider + opts, err := redis.ParseURL(cfg.Endpoints["standalone-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + opts.StreamingCredentialsProvider = cp + redisClient := redis.NewClient(opts) + + // Create second Redis client for cluster + clusterOpts, err := redis.ParseURL(cfg.Endpoints["cluster-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + clusterClient := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{clusterOpts.Addr}, + StreamingCredentialsProvider: cp, + }) + + pong, err := redisClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis: %v", err) + } + fmt.Printf("Successfully connected to Redis standalone: %s\n", pong) + + // Test cluster connection + clusterPong, err := clusterClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis cluster: %v", err) + } + fmt.Printf("Successfully connected to Redis cluster: %s\n", clusterPong) + + // Set a test key + err = redisClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key: %v", err) + } + + // Get the test key + val, err := redisClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key: %v", err) + } + fmt.Printf("Retrieved value from standalone: %s\n", val) + + // Set a test key in cluster + err = clusterClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key in cluster: %v", err) + } + + // Get the test key from cluster + clusterVal, err := clusterClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key from cluster: %v", err) + } + fmt.Printf("Retrieved value from cluster: %s\n", clusterVal) +} diff --git a/examples/entraid/endpoints.json b/examples/entraid/endpoints.json new file mode 100644 index 0000000..45555a4 --- /dev/null +++ b/examples/entraid/endpoints.json @@ -0,0 +1,109 @@ +{ + "standalone0": { + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6379" + ] + }, + "standalone0-tls": { + "username": "default", + "password": "foobared", + "tls": true, + "certificatesLocation": "redis1-2-5-8-sentinel/work/tls", + "endpoints": [ + "rediss://localhost:6390" + ] + }, + "standalone0-acl": { + "username": "acljedis", + "password": "fizzbuzz", + "tls": false, + "endpoints": [ + "redis://localhost:6379" + ] + }, + "standalone0-acl-tls": { + "username": "acljedis", + "password": "fizzbuzz", + "tls": true, + "certificatesLocation": "redis1-2-5-8-sentinel/work/tls", + "endpoints": [ + "rediss://localhost:6390" + ] + }, + "standalone1": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6380" + ] + }, + "standalone2-primary": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6381" + ] + }, + "standalone3-replica-of-standalone2": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6382" + ] + }, + "standalone4-replica-of-standalone1": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6383" + ] + }, + "standalone5-primary": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6384" + ] + }, + "standalone6-replica-of-standalone5": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6385" + ] + }, + "standalone7-with-lfu-policy": { + "username": "default", + "password": "foobared", + "tls": false, + "endpoints": [ + "redis://localhost:6386" + ] + }, + "standalone9": { + "tls": false, + "endpoints": [ + "redis://localhost:6388" + ] + }, + "standalone10-replica-of-standalone9": { + "tls": false, + "endpoints": [ + "redis://localhost:6389" + ] + }, + "modules-docker": { + "tls": false, + "endpoints": [ + "redis://localhost:6479" + ] + } +} \ No newline at end of file diff --git a/examples/entraid/managedidentity_system/go.mod b/examples/entraid/managedidentity_system/go.mod new file mode 100644 index 0000000..279c6ec --- /dev/null +++ b/examples/entraid/managedidentity_system/go.mod @@ -0,0 +1,32 @@ +module managedidentity_system + +go 1.23.4 + +require ( + config v0.0.0 + github.com/redis/go-redis-entraid v0.0.0-20250519132904-1e25b29e9a07 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +replace ( + config => ../config + github.com/redis/go-redis-entraid => ../../../ + github.com/redis/go-redis/v9 => github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/examples/entraid/managedidentity_system/go.sum b/examples/entraid/managedidentity_system/go.sum new file mode 100644 index 0000000..915d56f --- /dev/null +++ b/examples/entraid/managedidentity_system/go.sum @@ -0,0 +1,51 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/entraid/managedidentity_system/main.go b/examples/entraid/managedidentity_system/main.go new file mode 100644 index 0000000..f710cca --- /dev/null +++ b/examples/entraid/managedidentity_system/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "config" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Load configuration + cfg, err := config.LoadConfig(os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Create a managed identity credentials provider for system-assigned identity + cp, err := entraid.NewManagedIdentityCredentialsProvider(entraid.ManagedIdentityCredentialsProviderOptions{ + ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{ + // For system-assigned identity, we don't need to specify ClientID + Scopes: cfg.GetRedisScopes(), + ManagedIdentityType: "SystemAssigned", + }, + }) + if err != nil { + log.Fatalf("Failed to create credentials provider: %v", err) + } + + // Create Redis client with streaming credentials provider + opts, err := redis.ParseURL(cfg.Endpoints["standalone-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + opts.StreamingCredentialsProvider = cp + redisClient := redis.NewClient(opts) + + // Create second Redis client for cluster + clusterOpts, err := redis.ParseURL(cfg.Endpoints["cluster-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + clusterClient := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{clusterOpts.Addr}, + StreamingCredentialsProvider: cp, + }) + + // Test the connection + pong, err := redisClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis: %v", err) + } + fmt.Printf("Successfully connected to Redis standalone: %s\n", pong) + + // Test cluster connection + clusterPong, err := clusterClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis cluster: %v", err) + } + fmt.Printf("Successfully connected to Redis cluster: %s\n", clusterPong) + + // Set a test key + err = redisClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key: %v", err) + } + + // Get the test key + val, err := redisClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key: %v", err) + } + fmt.Printf("Retrieved value from standalone: %s\n", val) + + // Set a test key in cluster + err = clusterClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key in cluster: %v", err) + } + + // Get the test key from cluster + clusterVal, err := clusterClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key from cluster: %v", err) + } + fmt.Printf("Retrieved value from cluster: %s\n", clusterVal) +} diff --git a/examples/entraid/managedidentity_user/go.mod b/examples/entraid/managedidentity_user/go.mod new file mode 100644 index 0000000..847ae27 --- /dev/null +++ b/examples/entraid/managedidentity_user/go.mod @@ -0,0 +1,32 @@ +module managedidentity_user + +go 1.23.4 + +require ( + config v0.0.0 + github.com/redis/go-redis-entraid v0.0.0-20250519132904-1e25b29e9a07 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +replace ( + config => ../config + github.com/redis/go-redis-entraid => ../../../ + github.com/redis/go-redis/v9 => github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/examples/entraid/managedidentity_user/go.sum b/examples/entraid/managedidentity_user/go.sum new file mode 100644 index 0000000..915d56f --- /dev/null +++ b/examples/entraid/managedidentity_user/go.sum @@ -0,0 +1,51 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/entraid/managedidentity_user/main.go b/examples/entraid/managedidentity_user/main.go new file mode 100644 index 0000000..cdf0b4f --- /dev/null +++ b/examples/entraid/managedidentity_user/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "config" + + entraid "github.com/redis/go-redis-entraid" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis/v9" +) + +func main() { + ctx := context.Background() + + // Load configuration + cfg, err := config.LoadConfig(os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Create a managed identity credentials provider for user-assigned identity + cp, err := entraid.NewManagedIdentityCredentialsProvider(entraid.ManagedIdentityCredentialsProviderOptions{ + CredentialsProviderOptions: entraid.CredentialsProviderOptions{ + ClientID: cfg.AzureClientID, + }, + ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{ + ManagedIdentityType: "UserAssigned", + UserAssignedClientID: cfg.AzureUserAssignedManagedID, + Scopes: cfg.GetRedisScopes(), + }, + }) + if err != nil { + log.Fatalf("Failed to create credentials provider: %v", err) + } + + // Create Redis client with streaming credentials provider + opts, err := redis.ParseURL(cfg.Endpoints["standalone-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + opts.StreamingCredentialsProvider = cp + redisClient := redis.NewClient(opts) + + // Create second Redis client for cluster + clusterOpts, err := redis.ParseURL(cfg.Endpoints["cluster-entraid-acl"].Endpoints[0]) + if err != nil { + log.Fatalf("Failed to parse Redis URL: %v", err) + } + clusterClient := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{clusterOpts.Addr}, + StreamingCredentialsProvider: cp, + }) + + // Test the connection + pong, err := redisClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis: %v", err) + } + fmt.Printf("Successfully connected to Redis standalone: %s\n", pong) + + // Test cluster connection + clusterPong, err := clusterClient.Ping(ctx).Result() + if err != nil { + log.Fatalf("Failed to ping Redis cluster: %v", err) + } + fmt.Printf("Successfully connected to Redis cluster: %s\n", clusterPong) + + // Set a test key + err = redisClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key: %v", err) + } + + // Get the test key + val, err := redisClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key: %v", err) + } + fmt.Printf("Retrieved value from standalone: %s\n", val) + + // Set a test key in cluster + err = clusterClient.Set(ctx, "test-key", "test-value", 0).Err() + if err != nil { + log.Fatalf("Failed to set test key in cluster: %v", err) + } + + // Get the test key from cluster + clusterVal, err := clusterClient.Get(ctx, "test-key").Result() + if err != nil { + log.Fatalf("Failed to get test key from cluster: %v", err) + } + fmt.Printf("Retrieved value from cluster: %s\n", clusterVal) +} diff --git a/examples/entraid/run_examples.sh b/examples/entraid/run_examples.sh new file mode 100755 index 0000000..fbfb985 --- /dev/null +++ b/examples/entraid/run_examples.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Env should be set by the executor of the script +# Those examples are configured and executed in a private enviroment +# to verify the entraid intragration is working as expected. +# Common environment vddkariables +#export AZURE_REDIS_SCOPES="https://redis.azure.com/.default" +#export REDIS_ENDPOINVkTS_CONFIG_PATH="endpoints.json" + +# Exit on any error +set -e + +# Function to run an example +run_example() { + local example_dir=$1 + echo "Running $example_dir example..." + + if [ ! -d "$example_dir" ]; then + echo "Error: Directory $example_dir does not exist" + return 1 + fi + + if [ ! -f "$example_dir/main.go" ]; then + echo "Error: main.go not found in $example_dir" + return 1 + fi + + pushd "$example_dir" > /dev/null + go mod tidy + if ! go run main.go; then + echo "Error: $example_dir example failed" + popd > /dev/null + return 1 + fi + popd > /dev/null + echo "----------------------------------------" + return 0 +} + +# Track overall success +failed_examples=() + +# Run all examples in the directory +for example in */; do + # Skip config directory as it's not an example + if [ "$example" = "config/" ]; then + continue + fi + + # Remove trailing slash + example=${example%/} + + if ! run_example "$example"; then + failed_examples+=("$example") + fi +done + +# Report results +echo "----------------------------------------" +if [ ${#failed_examples[@]} -eq 0 ]; then + echo "All examples completed successfully!" + exit 0 +else + echo "The following examples failed:" + printf '%s\n' "${failed_examples[@]}" + exit 1 +fi diff --git a/examples/entraid/runscript_test/main.go b/examples/entraid/runscript_test/main.go new file mode 100644 index 0000000..c06f485 --- /dev/null +++ b/examples/entraid/runscript_test/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("OK") +} diff --git a/go.mod b/go.mod index 7e908f0..c394d2d 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,17 @@ -module github.com/redis-developer/go-redis-entraid +module github.com/redis/go-redis-entraid go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 - github.com/redis/go-redis/v9 v9.5.3-0.20250416091253-d0a8c76d8420 + github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b github.com/stretchr/testify v1.10.0 ) require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 36c644a..fe7563f 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,14 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkY github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -23,8 +29,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.5.3-0.20250416091253-d0a8c76d8420 h1:/dxO9rhmlhKP5pyI7omDH3QQzC0AppWxHT1w5TBsdTU= -github.com/redis/go-redis/v9 v9.5.3-0.20250416091253-d0a8c76d8420/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI= +github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= diff --git a/identity/azure_default_identity_provider.go b/identity/azure_default_identity_provider.go index 26e6a57..0a73cc5 100644 --- a/identity/azure_default_identity_provider.go +++ b/identity/azure_default_identity_provider.go @@ -7,7 +7,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/shared" ) // DefaultAzureIdentityProviderOptions represents the options for the DefaultAzureIdentityProvider. diff --git a/identity/azure_default_identity_provider_test.go b/identity/azure_default_identity_provider_test.go index b73b617..7a2d3dd 100644 --- a/identity/azure_default_identity_provider_test.go +++ b/identity/azure_default_identity_provider_test.go @@ -6,7 +6,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/identity/confidential_identity_provider.go b/identity/confidential_identity_provider.go index 532b0c8..10f1e0f 100644 --- a/identity/confidential_identity_provider.go +++ b/identity/confidential_identity_provider.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/shared" ) // ConfidentialIdentityProviderOptions represents the options for the confidential identity provider. diff --git a/identity/confidential_identity_provider_test.go b/identity/confidential_identity_provider_test.go index c1adf4f..389263c 100644 --- a/identity/confidential_identity_provider_test.go +++ b/identity/confidential_identity_provider_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/identity/managed_identity_provider.go b/identity/managed_identity_provider.go index cb5a4af..9ca3251 100644 --- a/identity/managed_identity_provider.go +++ b/identity/managed_identity_provider.go @@ -7,7 +7,7 @@ import ( mi "github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/shared" ) // ManagedIdentityClient is an interface that defines the methods for a managed identity client. diff --git a/manager/defaults.go b/manager/defaults.go index 10f4ba5..6b09cf2 100644 --- a/manager/defaults.go +++ b/manager/defaults.go @@ -9,8 +9,8 @@ import ( "time" "github.com/golang-jwt/jwt/v5" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" ) const ( diff --git a/manager/entraid_manager.go b/manager/entraid_manager.go index 4b8c716..8a7473e 100644 --- a/manager/entraid_manager.go +++ b/manager/entraid_manager.go @@ -6,9 +6,9 @@ import ( "sync" "time" - "github.com/redis-developer/go-redis-entraid/internal" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/internal" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" ) // entraidTokenManager is a struct that implements the TokenManager interface. diff --git a/manager/manager_test.go b/manager/manager_test.go index 8a49969..3f048a9 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -9,8 +9,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" "github.com/golang-jwt/jwt/v5" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" "github.com/stretchr/testify/mock" ) diff --git a/manager/token_manager.go b/manager/token_manager.go index f6c8620..8dcf55c 100644 --- a/manager/token_manager.go +++ b/manager/token_manager.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" ) // TokenManagerOptions is a struct that contains the options for the TokenManager. diff --git a/manager/token_manager_test.go b/manager/token_manager_test.go index 6a0944e..dba0a44 100644 --- a/manager/token_manager_test.go +++ b/manager/token_manager_test.go @@ -16,8 +16,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/providers.go b/providers.go index 2d053d7..4f1aebc 100644 --- a/providers.go +++ b/providers.go @@ -3,9 +3,9 @@ package entraid import ( "fmt" - "github.com/redis-developer/go-redis-entraid/identity" - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/shared" "github.com/redis/go-redis/v9/auth" ) diff --git a/providers_test.go b/providers_test.go index 604ddac..6716e02 100644 --- a/providers_test.go +++ b/providers_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/redis-developer/go-redis-entraid/identity" - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/shared" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/identity" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/shared" + "github.com/redis/go-redis-entraid/token" "github.com/redis/go-redis/v9/auth" "github.com/stretchr/testify/assert" ) diff --git a/shared/identity_provider_response.go b/shared/identity_provider_response.go index 86265ec..d60107a 100644 --- a/shared/identity_provider_response.go +++ b/shared/identity_provider_response.go @@ -5,8 +5,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" - "github.com/redis-developer/go-redis-entraid/internal" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/internal" + "github.com/redis/go-redis-entraid/token" ) const ( diff --git a/shared/identity_provider_response_test.go b/shared/identity_provider_response_test.go index 084ad09..2227506 100644 --- a/shared/identity_provider_response_test.go +++ b/shared/identity_provider_response_test.go @@ -6,7 +6,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/token" "github.com/stretchr/testify/assert" ) diff --git a/token/token.go b/token/token.go index e696f77..2e2d7a1 100644 --- a/token/token.go +++ b/token/token.go @@ -15,10 +15,10 @@ func New(username, password, rawToken string, expiresOn, receivedAt time.Time, t return &Token{ username: username, password: password, + rawToken: rawToken, expiresOn: expiresOn, receivedAt: receivedAt, ttl: ttl, - rawToken: rawToken, } } diff --git a/token_listener.go b/token_listener.go index 8515fda..171aadb 100644 --- a/token_listener.go +++ b/token_listener.go @@ -1,8 +1,8 @@ package entraid import ( - "github.com/redis-developer/go-redis-entraid/manager" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/manager" + "github.com/redis/go-redis-entraid/token" ) // entraidTokenListener implements the TokenListener interface for the entraidCredentialsProvider. diff --git a/token_listener_test.go b/token_listener_test.go index d2d996c..43ebf07 100644 --- a/token_listener_test.go +++ b/token_listener_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/redis-developer/go-redis-entraid/token" + "github.com/redis/go-redis-entraid/token" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" )