From f9c64122b9c2d5cfc0d0c6a8d4069d401c5db8c4 Mon Sep 17 00:00:00 2001 From: Bernd Verst Date: Mon, 1 Jul 2024 17:20:29 -0700 Subject: [PATCH] Adds EntraID auth support to all Redis Components (#3470) Signed-off-by: Bernd Verst --- .build-tools/go.mod | 4 +- .github/workflows/components-contrib-all.yml | 5 +- .github/workflows/components-contrib.yml | 5 +- .golangci.yml | 28 +--- Makefile | 4 +- bindings/redis/metadata.yaml | 15 ++ bindings/redis/redis.go | 2 +- bindings/redis/redis_test.go | 2 +- common/component/redis/redis.go | 166 +++++++++++++++++-- common/component/redis/redis_test.go | 1 + common/component/redis/settings.go | 4 + common/component/redis/v8client.go | 13 +- common/component/redis/v9client.go | 12 +- configuration/redis/metadata.yaml | 15 ++ configuration/redis/redis.go | 2 +- configuration/redis/redis_test.go | 8 +- go.mod | 4 +- lock/redis/standalone.go | 2 +- pubsub/redis/metadata.yaml | 17 +- pubsub/redis/redis.go | 2 +- state/redis/metadata.yaml | 15 ++ state/redis/redis.go | 2 +- tests/certification/go.mod | 4 +- tests/e2e/pubsub/jetstream/go.mod | 4 +- tests/utils/configupdater/redis/redis.go | 3 +- 25 files changed, 273 insertions(+), 66 deletions(-) diff --git a/.build-tools/go.mod b/.build-tools/go.mod index a9a023d901..99f47efc2a 100644 --- a/.build-tools/go.mod +++ b/.build-tools/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/components-contrib/build-tools -go 1.22.0 - -toolchain go1.22.2 +go 1.22.4 require ( github.com/dapr/components-contrib v0.0.0 diff --git a/.github/workflows/components-contrib-all.yml b/.github/workflows/components-contrib-all.yml index 8fc22b9948..0c220e8266 100644 --- a/.github/workflows/components-contrib-all.yml +++ b/.github/workflows/components-contrib-all.yml @@ -65,7 +65,7 @@ jobs: GOOS: ${{ matrix.target_os }} GOARCH: ${{ matrix.target_arch }} GOPROXY: https://proxy.golang.org - GOLANGCI_LINT_VER: "v1.55.2" + GOLANGCI_LINT_VER: "v1.59.1" strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] @@ -143,10 +143,11 @@ jobs: run: make check-component-metadata-schema-diff - name: Run golangci-lint if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true' - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v6.0.1 with: version: ${{ env.GOLANGCI_LINT_VER }} skip-cache: true + only-new-issues: true args: --timeout 15m - name: Run go mod tidy check diff if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux' && steps.skip_check.outputs.should_skip != 'true' diff --git a/.github/workflows/components-contrib.yml b/.github/workflows/components-contrib.yml index 1c62759075..64f0ac30b7 100644 --- a/.github/workflows/components-contrib.yml +++ b/.github/workflows/components-contrib.yml @@ -33,7 +33,7 @@ jobs: GOOS: linux GOARCH: amd64 GOPROXY: https://proxy.golang.org - GOLANGCI_LINT_VER: "v1.55.2" + GOLANGCI_LINT_VER: "v1.59.1" steps: - name: Check out code into the Go module directory if: ${{ steps.skip_check.outputs.should_skip != 'true' }} @@ -62,10 +62,11 @@ jobs: run: make check-component-metadata - name: Run golangci-lint if: steps.skip_check.outputs.should_skip != 'true' - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v6.0.1 with: version: ${{ env.GOLANGCI_LINT_VER }} skip-cache: true + only-new-issues: true args: --timeout 15m - name: Run go mod tidy check diff if: steps.skip_check.outputs.should_skip != 'true' diff --git a/.golangci.yml b/.golangci.yml index fba2a967d2..dcc8354447 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,7 +23,7 @@ run: # default value is empty list, but next dirs are always skipped independently # from this option's value: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs: + issues.exclude-dirs: - ^vendor$ # which files to skip: they will be analyzed, but issues from them @@ -37,7 +37,7 @@ run: # output configuration options output: # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" - format: tab + formats: tab # print lines of code with issue, default is true print-issued-lines: true @@ -71,9 +71,6 @@ linters-settings: statements: 40 govet: - # report about shadowed variables - check-shadowing: true - # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers @@ -86,6 +83,7 @@ linters-settings: # enable or disable analyzers by name enable: - atomicalign + - shadow enable-all: false disable: - shadow @@ -106,9 +104,6 @@ linters-settings: gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 10 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true dupl: # tokens count to trigger issue, 150 by default threshold: 100 @@ -121,6 +116,10 @@ linters-settings: rules: main: deny: + - pkg: "github.com/golang-jwt/jwt/v5" + desc: "must use github.com/lestrrat-go/jwx/v2/jwt" + - pkg: "github.com/golang-jwt/jwt/v4" + desc: "must use github.com/lestrrat-go/jwx/v2/jwt" - pkg: "github.com/Sirupsen/logrus" desc: "must use github.com/dapr/kit/logger" - pkg: "github.com/agrea/ptr" @@ -277,28 +276,24 @@ linters: - gocyclo - gocognit - godox - - interfacer - lll - - maligned - - scopelint - unparam - wsl + - mnd - gomnd - testpackage - - goerr113 + - err113 - nestif - nlreturn - exhaustive - exhaustruct - noctx - gci - - golint - tparallel - paralleltest - wrapcheck - tagliatelle - ireturn - - exhaustivestruct - errchkjson - contextcheck - gomoddirectives @@ -307,7 +302,6 @@ linters: - varnamelen - errorlint - forcetypeassert - - ifshort - maintidx - nilnil - predeclared @@ -320,10 +314,6 @@ linters: - asasalint - rowserrcheck - sqlclosecheck - - structcheck - - deadcode - - nosnakecase - - varcheck - goconst - tagalign - inamedparam diff --git a/Makefile b/Makefile index 14bc63cb2b..bb05248d64 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ export GH_LINT_VERSION := $(shell grep 'GOLANGCI_LINT_VER:' .github/workflows/co ifeq (,$(LINTER_BINARY)) INSTALLED_LINT_VERSION := "v0.0.0" else - INSTALLED_LINT_VERSION=v$(shell $(LINTER_BINARY) version | grep -Eo '([0-9]+\.)+[0-9]+' - || "") + INSTALLED_LINT_VERSION=v$(shell $(LINTER_BINARY) version | grep -Eo '([0-9]+\.)+[0-9]+' - | head -1 || "") endif # Build tools @@ -249,4 +249,4 @@ prettier-format: ################################################################################ .PHONY: conf-tests conf-tests: - CGO_ENABLED=$(CGO) go test -v -tags=conftests -count=1 ./tests/conformance \ No newline at end of file + CGO_ENABLED=$(CGO) go test -v -tags=conftests -count=1 ./tests/conformance diff --git a/bindings/redis/metadata.yaml b/bindings/redis/metadata.yaml index 4f591259b8..efc3407501 100644 --- a/bindings/redis/metadata.yaml +++ b/bindings/redis/metadata.yaml @@ -193,3 +193,18 @@ metadata: "-1" disables idle timeout check. default: "5m" example: "10m" +builtinAuthenticationProfiles: + - name: "azuread" + metadata: + - name: useEntraID + required: false + default: "false" + example: "true" + type: bool + description: | + If set, enables authentication to Azure Cache for Redis using Microsoft EntraID. The Redis server must explicitly enable EntraID authentication. Note that + Azure Cache for Redis also requires the use of TLS, so `enableTLS` should be set. No username or password should be set. + - name: enableTLS + required: true + description: Must be set to true if using EntraID + example: "true" \ No newline at end of file diff --git a/bindings/redis/redis.go b/bindings/redis/redis.go index e044fa9f1c..88a79452ef 100644 --- a/bindings/redis/redis.go +++ b/bindings/redis/redis.go @@ -44,7 +44,7 @@ func NewRedis(logger logger.Logger) bindings.OutputBinding { // Init performs metadata parsing and connection creation. func (r *Redis) Init(ctx context.Context, meta bindings.Metadata) (err error) { - r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(meta.Properties, metadata.BindingType) + r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(meta.Properties, metadata.BindingType, ctx, &r.logger) if err != nil { return err } diff --git a/bindings/redis/redis_test.go b/bindings/redis/redis_test.go index f38c2d4828..81232bef26 100644 --- a/bindings/redis/redis_test.go +++ b/bindings/redis/redis_test.go @@ -143,7 +143,7 @@ func TestInvokeDelete(t *testing.T) { rgetRep, err := c.DoRead(context.Background(), "GET", testKey) assert.Equal(t, redis.Nil, err) - assert.Equal(t, nil, rgetRep) + assert.Nil(t, rgetRep) } func TestCreateExpire(t *testing.T) { diff --git a/common/component/redis/redis.go b/common/component/redis/redis.go index 60071bc808..c16f0b351b 100644 --- a/common/component/redis/redis.go +++ b/common/component/redis/redis.go @@ -15,15 +15,23 @@ package redis import ( "context" + "errors" "fmt" "strconv" "strings" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/cenkalti/backoff/v4" + "github.com/lestrrat-go/jwx/v2/jwt" "golang.org/x/mod/semver" + "github.com/dapr/components-contrib/common/authentication/azure" "github.com/dapr/components-contrib/configuration" "github.com/dapr/components-contrib/metadata" + kitlogger "github.com/dapr/kit/logger" + kitretry "github.com/dapr/kit/retry" ) const ( @@ -82,6 +90,7 @@ type RedisClient interface { XClaimResult(ctx context.Context, stream string, group string, consumer string, minIdleTime time.Duration, messageIDs []string) ([]RedisXMessage, error) TxPipeline() RedisPipeliner TTLResult(ctx context.Context, key string) (time.Duration, error) + AuthACL(ctx context.Context, username, password string) error } type ConfigurationSubscribeArgs struct { @@ -94,8 +103,8 @@ type ConfigurationSubscribeArgs struct { Stop chan struct{} } -func ParseClientFromProperties(properties map[string]string, componentType metadata.ComponentType) (client RedisClient, settings *Settings, err error) { - settings = &Settings{} +func ParseClientFromProperties(properties map[string]string, componentType metadata.ComponentType, ctx context.Context, logger *kitlogger.Logger) (RedisClient, *Settings, error) { + settings := Settings{} // upgrade legacy metadata properties and set defaults switch componentType { @@ -112,9 +121,9 @@ func ParseClientFromProperties(properties map[string]string, componentType metad if properties[redisMinRetryIntervalKey] == "" { if properties[maxRetryBackoffKey] != "" { // due to different duration formats, do not simply change the key name - parsedVal, parseErr := strconv.ParseInt(properties[maxRetryBackoffKey], 10, 0) - if parseErr != nil { - return nil, nil, fmt.Errorf("redis store error: can't parse maxRetryBackoff field: %s", parseErr) + parsedVal, err := strconv.ParseInt(properties[maxRetryBackoffKey], 10, 0) + if err != nil { + return nil, nil, fmt.Errorf("redis store error: can't parse maxRetryBackoff field: %s", err) } settings.RedisMinRetryInterval = Duration(time.Duration(parsedVal)) } @@ -132,7 +141,7 @@ func ParseClientFromProperties(properties map[string]string, componentType metad settings.Concurrency = 10 } - err = settings.Decode(properties) + err := settings.Decode(properties) if err != nil { return nil, nil, fmt.Errorf("redis client configuration error: %w", err) } @@ -157,6 +166,14 @@ func ParseClientFromProperties(properties map[string]string, componentType metad // if there was an error we would try to interpret it as a duration string, which was already done in Decode() } } + var tokenExpires *time.Time + var tokenCredential *azcore.TokenCredential + if settings.UseEntraID { + tokenExpires, tokenCredential, err = settings.GetEntraIDCredentialAndSetInitialTokenAsPassword(ctx, &properties) + if err != nil { + return nil, nil, err + } + } var c RedisClient newClientFunc := newV8Client @@ -164,16 +181,19 @@ func ParseClientFromProperties(properties map[string]string, componentType metad newClientFunc = newV8FailoverClient } - c, err = newClientFunc(settings) + c, err = newClientFunc(&settings) if err != nil { return nil, nil, fmt.Errorf("redis client configuration error: %w", err) } - version, versionErr := GetServerVersion(c) - c.Close() // close the client to avoid leaking connections + version, err := GetServerVersion(c) + closeErr := c.Close() // close the client to avoid leaking connections + if closeErr != nil { + return nil, nil, closeErr + } useNewClient := false - if versionErr != nil { + if err != nil { // we couldn't query the server version, so we will assume the v8 client is not supported useNewClient = true } else if semver.Compare("v"+version, "v7.0.0") > -1 { @@ -187,11 +207,133 @@ func ParseClientFromProperties(properties map[string]string, componentType metad newClientFunc = newV9FailoverClient } } - c, err = newClientFunc(settings) + c, err = newClientFunc(&settings) + if err != nil { + return nil, nil, fmt.Errorf("redis client configuration error: %w", err) + } + + // start the token refresh goroutine + + if settings.UseEntraID { + StartEntraIDTokenRefreshBackgroundRoutine(c, settings.Username, *tokenExpires, tokenCredential, ctx, logger) + } + return c, &settings, nil +} + +func StartEntraIDTokenRefreshBackgroundRoutine(client RedisClient, username string, nextExpiration time.Time, cred *azcore.TokenCredential, parentCtx context.Context, logger *kitlogger.Logger) { + go func(cred *azcore.TokenCredential, username string, logger *kitlogger.Logger) { + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + backoffConfig := kitretry.DefaultConfig() + backoffConfig.MaxRetries = 3 + backoffConfig.Policy = kitretry.PolicyExponential + + var backoffManager backoff.BackOff + const refreshGracePeriod = 2 * time.Minute + tokenRefreshDuration := time.Until(nextExpiration.Add(-refreshGracePeriod)) + + (*logger).Debugf("redis client: starting entraID token refresh loop") + + for { + (*logger).Debugf("redis client: next entraID token refresh: %v", tokenRefreshDuration) + select { + case <-ctx.Done(): + (*logger).Infof("redis client: entraID token refresh stopped due to context cancellation") + return + case <-time.After(tokenRefreshDuration): + (*logger).Debug("redis client: refreshing entraID token") + // Get a new access token + backoffManager = backoffConfig.NewBackOffWithContext(ctx) + var token azcore.AccessToken + tokenErr := kitretry.NotifyRecover( + func() error { + var innerTokenErr error + token, innerTokenErr = (*cred).GetToken(ctx, policy.TokenRequestOptions{ + Scopes: []string{"https://redis.azure.com/.default"}, + }) + return innerTokenErr + }, + backoffManager, + func(err error, _ time.Duration) { + (*logger).Debugf("redis client: entraID token acquisition failed with error: %v. Retrying...", err) + }, + func() { + (*logger).Debug("redis client: entraID token acquisition succeeded after error") + }, + ) + if tokenErr != nil { + _ = client.Close() + (*logger).Fatalf("redis client: entraID token acquisition failed: %v", tokenErr) + return + } + + // Use the new access token via the Redis AUTH command + backoffManager = backoffConfig.NewBackOffWithContext(ctx) + authErr := kitretry.NotifyRecover( + func() error { + var innerAuthErr error + innerAuthErr = client.AuthACL(ctx, username, token.Token) + return innerAuthErr + }, + backoffManager, + func(err error, _ time.Duration) { + (*logger).Debugf("redis client: entraID auth failed with error: %v. Retrying...", err) + }, + func() { + (*logger).Debug("redis client: entraID auth succeeded after error") + }, + ) + if authErr != nil { + _ = client.Close() + (*logger).Fatalf("redis client: entraID auth failed: %v", authErr) + return + } + // Since the entraID auth succeeded we are setting the duration to wait for the next iteration of the refresh loop + + (*logger).Debugf("redis client: entraID auth token successfully refreshed with the server") + + tokenRefreshDuration = time.Until(token.ExpiresOn.Add(-refreshGracePeriod)) + } + } + }(cred, username, logger) +} + +func (s *Settings) GetEntraIDCredentialAndSetInitialTokenAsPassword(ctx context.Context, properties *map[string]string) (*time.Time, *azcore.TokenCredential, error) { + if len(s.Password) > 0 || len(s.Username) > 0 { + return nil, nil, errors.New( + "redis client configuration error: username or password must not be specified when using Entra ID authentication") + } + envSettings, err := azure.NewEnvironmentSettings(*properties) + if err != nil { + return nil, nil, fmt.Errorf("redis client configuration error: %w", err) + } + cred, err := envSettings.GetTokenCredential() + if err != nil { + return nil, nil, fmt.Errorf("redis client configuration error: %w", err) + } + + token, err := cred.GetToken(ctx, policy.TokenRequestOptions{ + Scopes: []string{"https://redis.azure.com/.default"}, + }) if err != nil { return nil, nil, fmt.Errorf("redis client configuration error: %w", err) } - return c, settings, nil + + s.Password = token.Token + + // This token has already been validated by EntraID. We use insecure parsing to get the object ID. + parsedToken, err := jwt.ParseString(token.Token, jwt.WithVerify(false), jwt.WithValidate(false)) + if err != nil { + return nil, nil, fmt.Errorf("redis client configuration error: %w", err) + } + objectID, found := parsedToken.Get("oid") + + if found { + s.Username = objectID.(string) + } else { + return nil, nil, errors.New("redis client configuration error: could not parse object ID from Auth token") + } + return &token.ExpiresOn, &cred, nil } func ClientHasJSONSupport(c RedisClient) bool { diff --git a/common/component/redis/redis_test.go b/common/component/redis/redis_test.go index cb74d8bd7f..1445dc2cfc 100644 --- a/common/component/redis/redis_test.go +++ b/common/component/redis/redis_test.go @@ -105,6 +105,7 @@ func TestParseRedisMetadata(t *testing.T) { assert.Equal(t, 1*time.Second, time.Duration(m.IdleCheckFrequency)) assert.True(t, m.Failover) assert.Equal(t, "master", m.SentinelMasterName) + assert.False(t, m.UseEntraID) }) // TODO: Refactor shared redis code to throw error for missing properties diff --git a/common/component/redis/settings.go b/common/component/redis/settings.go index f9599c5856..e154982050 100644 --- a/common/component/redis/settings.go +++ b/common/component/redis/settings.go @@ -101,6 +101,10 @@ type Settings struct { // The max len of stream MaxLenApprox int64 `mapstructure:"maxLenApprox" mdonly:"pubsub"` + + // EntraID / AzureAD Authentication based on the shared code which essentially uses the DefaultAzureCredential + // from the official Azure Identity SDK for Go + UseEntraID bool `mapstructure:"useEntraID" mapstructurealiases:"useAzureAD"` } func (s *Settings) Decode(in interface{}) error { diff --git a/common/component/redis/v8client.go b/common/component/redis/v8client.go index b95de56ce1..bca21a6b5c 100644 --- a/common/component/redis/v8client.go +++ b/common/component/redis/v8client.go @@ -316,6 +316,12 @@ func (c v8Client) TTLResult(ctx context.Context, key string) (time.Duration, err return c.client.TTL(writeCtx, key).Result() } +func (c v8Client) AuthACL(ctx context.Context, username, password string) error { + pipeline := c.client.Pipeline() + statusCmd := pipeline.AuthACL(ctx, username, password) + return statusCmd.Err() +} + func newV8FailoverClient(s *Settings) (RedisClient, error) { if s == nil { return nil, nil @@ -340,10 +346,9 @@ func newV8FailoverClient(s *Settings) (RedisClient, error) { IdleTimeout: time.Duration(s.IdleTimeout), } - /* #nosec */ if s.EnableTLS { opts.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { opts.TLSConfig.Certificates = []tls.Certificate{*cert} @@ -397,7 +402,7 @@ func newV8Client(s *Settings) (RedisClient, error) { /* #nosec */ if s.EnableTLS { options.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { options.TLSConfig.Certificates = []tls.Certificate{*cert} @@ -437,7 +442,7 @@ func newV8Client(s *Settings) (RedisClient, error) { /* #nosec */ if s.EnableTLS { options.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { options.TLSConfig.Certificates = []tls.Certificate{*cert} diff --git a/common/component/redis/v9client.go b/common/component/redis/v9client.go index 16177367c2..89044648d7 100644 --- a/common/component/redis/v9client.go +++ b/common/component/redis/v9client.go @@ -317,6 +317,12 @@ func (c v9Client) TTLResult(ctx context.Context, key string) (time.Duration, err return c.client.TTL(writeCtx, key).Result() } +func (c v9Client) AuthACL(ctx context.Context, username, password string) error { + pipeline := c.client.Pipeline() + statusCmd := pipeline.AuthACL(ctx, username, password) + return statusCmd.Err() +} + func newV9FailoverClient(s *Settings) (RedisClient, error) { if s == nil { return nil, nil @@ -344,7 +350,7 @@ func newV9FailoverClient(s *Settings) (RedisClient, error) { /* #nosec */ if s.EnableTLS { opts.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { opts.TLSConfig.Certificates = []tls.Certificate{*cert} @@ -400,7 +406,7 @@ func newV9Client(s *Settings) (RedisClient, error) { if s.EnableTLS { /* #nosec */ options.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { options.TLSConfig.Certificates = []tls.Certificate{*cert} @@ -440,7 +446,7 @@ func newV9Client(s *Settings) (RedisClient, error) { if s.EnableTLS { /* #nosec */ options.TLSConfig = &tls.Config{ - InsecureSkipVerify: s.EnableTLS, + InsecureSkipVerify: s.EnableTLS, //nolint:gosec } err := s.SetCertificate(func(cert *tls.Certificate) { options.TLSConfig.Certificates = []tls.Certificate{*cert} diff --git a/configuration/redis/metadata.yaml b/configuration/redis/metadata.yaml index a00e3e82b5..6abccb1300 100644 --- a/configuration/redis/metadata.yaml +++ b/configuration/redis/metadata.yaml @@ -181,3 +181,18 @@ metadata: "-1" disables idle timeout check. default: "5m" example: "10m" +builtinAuthenticationProfiles: + - name: "azuread" + metadata: + - name: useEntraID + required: false + default: "false" + example: "true" + type: bool + description: | + If set, enables authentication to Azure Cache for Redis using Microsoft EntraID. The Redis server must explicitly enable EntraID authentication. Note that + Azure Cache for Redis also requires the use of TLS, so `enableTLS` should be set. No username or password should be set. + - name: enableTLS + required: true + description: Must be set to true if using EntraID + example: "true" \ No newline at end of file diff --git a/configuration/redis/redis.go b/configuration/redis/redis.go index 77ec0cd967..063ac001fe 100644 --- a/configuration/redis/redis.go +++ b/configuration/redis/redis.go @@ -64,7 +64,7 @@ func NewRedisConfigurationStore(logger logger.Logger) configuration.Store { // Init does metadata and connection parsing. func (r *ConfigurationStore) Init(ctx context.Context, metadata configuration.Metadata) error { var err error - r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.ConfigurationStoreType) + r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.ConfigurationStoreType, ctx, &r.logger) if err != nil { return err } diff --git a/configuration/redis/redis_test.go b/configuration/redis/redis_test.go index a59f4d7b11..03665171c1 100644 --- a/configuration/redis/redis_test.go +++ b/configuration/redis/redis_test.go @@ -302,7 +302,9 @@ func Test_parseRedisMetadata(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, got, err := redisComponent.ParseClientFromProperties(tt.args.meta.Properties, contribMetadata.ConfigurationStoreType) + ctx := context.Background() + log := logger.NewLogger("dapr.components") + _, got, err := redisComponent.ParseClientFromProperties(tt.args.meta.Properties, contribMetadata.ConfigurationStoreType, ctx, &log) if (err != nil) != tt.wantErr { t.Errorf("edisComponent.ParseClientFromProperties error = %v, wantErr %v", err, tt.wantErr) return @@ -321,6 +323,8 @@ func Test_parseRedisMetadata(t *testing.T) { } func setupMiniredis() (*miniredis.Miniredis, redisComponent.RedisClient) { + ctx := context.Background() + log := logger.NewLogger("dapr.components") s, err := miniredis.Run() if err != nil { panic(err) @@ -329,7 +333,7 @@ func setupMiniredis() (*miniredis.Miniredis, redisComponent.RedisClient) { "redisHost": s.Addr(), "redisDB": "0", } - redisClient, _, _ := redisComponent.ParseClientFromProperties(props, contribMetadata.ConfigurationStoreType) + redisClient, _, _ := redisComponent.ParseClientFromProperties(props, contribMetadata.ConfigurationStoreType, ctx, &log) return s, redisClient } diff --git a/go.mod b/go.mod index 23df0fd804..a525ed94c9 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/components-contrib -go 1.22.0 - -toolchain go1.22.2 +go 1.22.4 require ( cloud.google.com/go/datastore v1.15.0 diff --git a/lock/redis/standalone.go b/lock/redis/standalone.go index 24945e604d..acb4d988dc 100644 --- a/lock/redis/standalone.go +++ b/lock/redis/standalone.go @@ -50,7 +50,7 @@ func NewStandaloneRedisLock(logger logger.Logger) lock.Store { // Init StandaloneRedisLock. func (r *StandaloneRedisLock) InitLockStore(ctx context.Context, metadata lock.Metadata) (err error) { // Create the client - r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.LockStoreType) + r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.LockStoreType, ctx, &r.logger) if err != nil { return err } diff --git a/pubsub/redis/metadata.yaml b/pubsub/redis/metadata.yaml index 67067993a4..91628099df 100644 --- a/pubsub/redis/metadata.yaml +++ b/pubsub/redis/metadata.yaml @@ -168,4 +168,19 @@ metadata: required: false description: Maximum number of items inside a stream.The old entries are automatically evicted when the specified length is reached, so that the stream is left at a constant size. Defaults to unlimited. example: "10000" - type: number \ No newline at end of file + type: number +builtinAuthenticationProfiles: + - name: "azuread" + metadata: + - name: useEntraID + required: false + default: "false" + example: "true" + type: bool + description: | + If set, enables authentication to Azure Cache for Redis using Microsoft EntraID. The Redis server must explicitly enable EntraID authentication. Note that + Azure Cache for Redis also requires the use of TLS, so `enableTLS` should be set. No username or password should be set. + - name: enableTLS + required: true + description: Must be set to true if using EntraID + example: "true" \ No newline at end of file diff --git a/pubsub/redis/redis.go b/pubsub/redis/redis.go index d6f2c7492a..310eaddad1 100644 --- a/pubsub/redis/redis.go +++ b/pubsub/redis/redis.go @@ -76,7 +76,7 @@ func NewRedisStreams(logger logger.Logger) pubsub.PubSub { func (r *redisStreams) Init(ctx context.Context, metadata pubsub.Metadata) error { var err error - r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.PubSubType) + r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, contribMetadata.PubSubType, ctx, &r.logger) if err != nil { return err } diff --git a/state/redis/metadata.yaml b/state/redis/metadata.yaml index 170b11e932..fbee1e0525 100644 --- a/state/redis/metadata.yaml +++ b/state/redis/metadata.yaml @@ -164,3 +164,18 @@ metadata: description: Indexing schemas for querying JSON objects example: "see Querying JSON objects" type: string +builtinAuthenticationProfiles: + - name: "azuread" + metadata: + - name: useEntraID + required: false + default: "false" + example: "true" + type: bool + description: | + If set, enables authentication to Azure Cache for Redis using Microsoft EntraID. The Redis server must explicitly enable EntraID authentication. Note that + Azure Cache for Redis also requires the use of TLS, so `enableTLS` should be set. No username or password should be set. + - name: enableTLS + required: true + description: Must be set to true if using EntraID + example: "true" \ No newline at end of file diff --git a/state/redis/redis.go b/state/redis/redis.go index 75f0112b5e..318e4ea129 100644 --- a/state/redis/redis.go +++ b/state/redis/redis.go @@ -131,7 +131,7 @@ func (r *StateStore) Ping(ctx context.Context) error { // Init does metadata and connection parsing. func (r *StateStore) Init(ctx context.Context, metadata state.Metadata) error { var err error - r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, daprmetadata.StateStoreType) + r.client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(metadata.Properties, daprmetadata.StateStoreType, ctx, &r.logger) if err != nil { return err } diff --git a/tests/certification/go.mod b/tests/certification/go.mod index d4eb3984d1..d8f7d32cc6 100644 --- a/tests/certification/go.mod +++ b/tests/certification/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/components-contrib/tests/certification -go 1.22.3 - -toolchain go1.22.4 +go 1.22.4 require ( cloud.google.com/go/pubsub v1.36.1 diff --git a/tests/e2e/pubsub/jetstream/go.mod b/tests/e2e/pubsub/jetstream/go.mod index 44e817d2c1..90fd420743 100644 --- a/tests/e2e/pubsub/jetstream/go.mod +++ b/tests/e2e/pubsub/jetstream/go.mod @@ -1,8 +1,6 @@ module github.com/dapr/components-contrib/tests/e2e/pubsub/jetstream -go 1.22.0 - -toolchain go1.22.2 +go 1.22.4 require ( github.com/dapr/components-contrib v1.10.6-0.20230403162214-9ee9d56cb7ea diff --git a/tests/utils/configupdater/redis/redis.go b/tests/utils/configupdater/redis/redis.go index 4d2014fc4d..383f530db9 100644 --- a/tests/utils/configupdater/redis/redis.go +++ b/tests/utils/configupdater/redis/redis.go @@ -48,7 +48,8 @@ func getRedisValuesFromItems(items map[string]*configuration.Item) []interface{} func (r *ConfigUpdater) Init(props map[string]string) error { var err error - r.Client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(props, metadata.ConfigurationStoreType) + ctx := context.Background() + r.Client, r.clientSettings, err = rediscomponent.ParseClientFromProperties(props, metadata.ConfigurationStoreType, ctx, &r.logger) if err != nil { return err }