Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RedisConnectionFactory with IAM auth for Elasticache #2769

Closed
chandank-nu opened this issue Nov 13, 2023 · 8 comments
Closed

RedisConnectionFactory with IAM auth for Elasticache #2769

chandank-nu opened this issue Nov 13, 2023 · 8 comments
Labels
status: invalid An issue that we don't feel is valid

Comments

@chandank-nu
Copy link

chandank-nu commented Nov 13, 2023

Hi,
We recently switched from Password based Auth to IAM auth for our Elasticache cluster enabled Redis. I was able to supply Sig4 signed request as password and connect to Elasticache. This works well for first 12 hours as IAM auth enabled Elasticache disconnects after 12 hours automatically.

As I created a Custom RedisClusterConfiguration while creating RedisConnectionFactory so was expecting Springboot-data-redis and Lettuce to reconnect automatically but it seems like the password / "Sig4 signed request" that was generated for the first time is being cached somewhere.

Here is my code.... I don't see getPassword() being called while trying to reconnect.. I'm getting WRONG username - password error in Logs.

public class CustomRedisClusterConfiguration extends RedisClusterConfiguration {
    private static final Logger LOG = LoggerFactory.getLogger(CustomRedisClusterConfiguration.class);

    private final String replicationGroupId;

    private final String region;

    private final char[] password;

    public CustomRedisClusterConfiguration(Collection<String> clusterNodes,
                                           String replicationGroupId,
                                           String region,
                                           char[] password)
    {
        super(clusterNodes);
        this.replicationGroupId = replicationGroupId;
        this.region = region;
        this.password = password;
    }

    @NotNull
    @Override
    public RedisPassword getPassword() {
        LOG.info("Get Password called...");
        if (this.password == null || this.password.length == 0 ) {
            LOG.info("Using IAM Authentication mechanism for redis connection");
            //get password from IAM Token Request API

            AwsCredentialsProvider defaultCredentialsProvider =
                    DefaultCredentialsProvider.builder().build();

            //Signed URI as Auth token
            String authToken = ElasticCacheIamAuthUtils.toSignedRequestUri(defaultCredentialsProvider
                            .resolveCredentials(),
                    this.getUsername(),
                    this.region,
                    replicationGroupId);

            LOG.info("Auth Token-1 {}", authToken);
            return RedisPassword.of(authToken);
        } else {
            return RedisPassword.of(this.password);
        }

    }
}

Any thoughts how we can supply new password (generated through code) every time it tries to retry connecting to Redis.

Thanks for taking a look at this.

Thanks,
Chandan

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 13, 2023
@mp911de
Copy link
Member

mp911de commented Nov 13, 2023

What client are you using? Jedis uses a fixed password while Lettuce provides a Credentials Supplier API that you can use without subclassing any Spring Data Redis utilities.

@mp911de mp911de added the status: waiting-for-feedback We need additional information before we can continue label Nov 13, 2023
@chandank-nu
Copy link
Author

What client are you using? Jedis uses a fixed password while Lettuce provides a Credentials Supplier API that you can use without subclassing any Spring Data Redis utilities.

@mp911de thanks for your response. We're using Lettuce. How can I use credentials supplier API while creating LettuceConnectionFactory? Kindly suggest.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Nov 14, 2023
@mp911de
Copy link
Member

mp911de commented Nov 14, 2023

You have to provide a RedisCredentialsProviderFactory via LettuceClientConfiguration. That could look like:

class MyCredentialsProviderFactory implements RedisCredentialsProviderFactory {

	@Override
	public RedisCredentialsProvider createCredentialsProvider(RedisConfiguration redisConfiguration) {
		Supplier<RedisCredentials> supplier = …;
		return () -> Mono.fromSupplier(supplier);
	}

	@Override
	public RedisCredentialsProvider createSentinelCredentialsProvider(RedisSentinelConfiguration redisConfiguration) {
		Supplier<RedisCredentials> supplier = …;
		return () -> Mono.fromSupplier(supplier);
	}
}

LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
    .redisCredentialsProviderFactory(new MyCredentialsProviderFactory())
    .build();
LettuceConnectionFactory connFactory = new LettuceConnectionFactory(redisConfiguration, clientConfiguration);

@chandank-nu
Copy link
Author

@mp911de thanks for sharing the code snippet.. We're currently using springboot 2.7 and RedisCredentialsProviderFactory is not present in 2.7.11

@mp911de
Copy link
Member

mp911de commented Nov 14, 2023

RedisCredentialsProviderFactory was introduced with version 3 and requires an upgrade to Java 17 and Spring Boot 3.

@mp911de mp911de added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided labels Nov 14, 2023
@mp911de mp911de closed this as not planned Won't fix, can't repro, duplicate, stale Nov 14, 2023
@chandank-nu
Copy link
Author

@mp911de We migrated to Java 17 and Springboot 3.1.5 but still facing the same issue.
As per your suggestion I implemented RedisCredentialsProviderFactory but the reconnect attempts are failing. The initial connection is a success but I don't see the LettuceConnectionFactory referring to Custom RedisCredentialProviderFactory while reconnect attempt.

` class IamCredentialsProviderFactory implements RedisCredentialsProviderFactory {

    final String replicationGroupId;
    final String userId;
    final String region;
    final char[] password;

    private static final Logger LOG = LoggerFactory.getLogger(IamCredentialsProviderFactory.class);
    IamCredentialsProviderFactory(String replicationGroupId, String userId, String region, char[] password) {
        this.replicationGroupId = replicationGroupId;
        this.userId = userId;
        this.region = region;
        this.password = password;
    }
    @Override
    public RedisCredentialsProvider createCredentialsProvider(RedisConfiguration redisConfiguration) {
        LOG.info("Inside createCredentialsProvider");
        if (password == null || password.length == 0) {
            AwsCredentialsProvider defaultCredentialsProvider =
                    DefaultCredentialsProvider.create();

            String password = ElasticCacheIamAuthUtils
                    .toSignedRequestUri(defaultCredentialsProvider.resolveCredentials(),
                            userId, region, replicationGroupId);

            LOG.info("Inside createCredentialsProvider, password {}", password);
            Supplier<RedisCredentials> supplier = () -> RedisCredentials.just(userId, password);
            return () -> Mono.fromSupplier(supplier);
        } else {
            return redisConfiguration instanceof RedisConfiguration.WithAuthentication &&
                    ((RedisConfiguration.WithAuthentication)redisConfiguration).getPassword().isPresent() ? RedisCredentialsProvider.from(() -> {
                RedisConfiguration.WithAuthentication withAuthentication = (RedisConfiguration.WithAuthentication)redisConfiguration;
                return RedisCredentials.just(withAuthentication.getUsername(), withAuthentication.getPassword().get());
            }) : () -> Mono.just(RedisCredentialsProviderFactory.AbsentRedisCredentials.ANONYMOUS);

        }


    }`

@mp911de
Copy link
Member

mp911de commented Dec 1, 2023

String password = ElasticCacheIamAuthUtils
                    .toSignedRequestUri(defaultCredentialsProvider.resolveCredentials(),
                            userId, region, replicationGroupId);

            LOG.info("Inside createCredentialsProvider, password {}", password);
            Supplier<RedisCredentials> supplier = () -> RedisCredentials.just(userId, password);
            return () -> Mono.fromSupplier(supplier);

This captures the password that has been created and each request to provide new credentials returns the same password.

You need to return the password within the Supplier, or better, within the Mono.

@chandank-nu
Copy link
Author

Thanks! I'll try this today and confirm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants