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
ERR unknown command 'CONFIG' when using Secured Redis #124
Comments
Thanks for the report @danveloper! This indeed seems to be a bug with the UPDATE Fixing in 1.0.1As of Spring Session 1.0.1 this can be disabled by exposing An XML Configuration example <util:constant
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/> A Java Configuration example @Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
} Fixing the IssueI'm debating what the best approach to fixing this would be though and wondering what your thoughts were @danveloper. There is certainly a need for a fix, so I'm not debating that we need to fix something. However, I like the fact that it updates the Redis configuration by default for two reasons:
My initial thoughts on how we should update the configuration is:
WorkaroundIn the meantime, a workaround is to remove import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.SessionMessageListener;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration {
@Value("${spring.session.maxInactive ?: 1800}")
private Integer maxInactiveIntervalInSeconds;
private HttpSessionStrategy httpSessionStrategy;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(redisSessionMessageListener(),
Arrays.asList(new PatternTopic("__keyevent@*:del"),new PatternTopic("__keyevent@*:expired")));
return container;
}
@Bean
public SessionMessageListener redisSessionMessageListener() {
return new SessionMessageListener(eventPublisher);
}
@Bean
public RedisTemplate<String,ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, ExpiringSession> sessionRedisTemplate) {
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(sessionRedisTemplate);
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
return sessionRepository;
}
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(sessionRepository);
if(httpSessionStrategy != null) {
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
}
return sessionRepositoryFilter;
}
@Autowired(required = false)
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
this.httpSessionStrategy = httpSessionStrategy;
}
} If you are not using the import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.SessionMessageListener;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
@Configuration
public class RedisHttpSessionConfiguration {
@Value("${spring.session.maxInactive ?: 1800}")
private Integer maxInactiveIntervalInSeconds;
private HttpSessionStrategy httpSessionStrategy;
@Bean
public RedisTemplate<String,ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, ExpiringSession> sessionRedisTemplate) {
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(sessionRedisTemplate);
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
return sessionRepository;
}
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(sessionRepository);
if(httpSessionStrategy != null) {
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
}
return sessionRepositoryFilter;
}
@Autowired(required = false)
public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
this.httpSessionStrategy = httpSessionStrategy;
}
} |
I think it should be enabled by default, but fail gracefully with a warning. This would allow the same configuration to be used between dev and prod, where dev would JustWork(tm) and prod would require some manual intervention (which would be obvious from the warning). I was able to work around the problem by subclassing the package org.springframework.session.data.redis.config.annotation.web.http
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.type.AnnotationMetadata
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.session.ExpiringSession
import org.springframework.session.data.redis.RedisOperationsSessionRepository
@Configuration
class GateRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
@Value('${session.expiration:1800}')
int expiration
public void setImportMetadata(AnnotationMetadata importMetadata) {
}
@Bean
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, ExpiringSession> sessionRedisTemplate) {
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(sessionRedisTemplate);
sessionRepository.setDefaultMaxInactiveInterval(expiration);
return sessionRepository;
}
@Override
public RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer enableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory) {
null
}
} For posterity, here are the steps for enabling the keyspace notifications on AWS: Log into the AWS console and choose the ElastiCache serviceChoose the Cache Parameter Groups and click Create Parameter GroupGive the new group and name and description and click CreateWith the new parameter group created, select it and click Edit ParametersPage through the parameters until you find notify-keyspace-events and enter "eA" in the Value field and click Save ChangesChoose Cache Clusters from the context navigation and create a new Redis cache clusterWhen specifying your cluster detail, choose the newly created parameter group |
@danveloper I'm glad you were able to work around the issue. Thank you for your feedback and for the useful directions on enabling keyspace notifications in AWS. I have concerns with the configuration not failing when it tries to update the Redis configuration. Developers likely did thorough testing within another environment. For example, they might be integrating with WebSocket support and validate that when an HttpSession is terminated the WebSocket is indeed closing properly. This means they are confident that everything is working by the time the get to production. Many users may not even notice a warning being logged. Without Redis keyspace notifications being enabled, the applications WebSocket connections will not properly closed when the HttpSession is destroyed. Obviously developers should read the instructions completely, look for warnings, and perform some smoke tests in production to avoid such issues. However, I fear that by not failing we are setting up users for failure. A compromise might be to require users to explicitly set a property to allow "fall back". This means users would be aware that they need to do something in production. Of course this is one more piece of configuration users would need. Thoughts? |
Could it provide a different strategy for ascertaining the database's configuration? Maybe in the absence of the I'd probably prefer a slower startup over managing feature flags between dev & prod. If this isn't possible or is otherwise infeasible, then feature flag might be the only way to go. |
Great idea! I think this may be doable, but we will see when I get into the details. Thanks for your feedback! |
RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer is not visible for outsiders extending RedisHttpSessionConfiguration. The workaround doesn't work. Can you help me with this? |
@njariwala You would need to place it in the same package as illustrated in the example above. |
If the class is semantically usable for non-core extensions, it seems like a bad practice to trespass on the distribution package, especially since that can fail in interesting ways with particular classloader hierarchies. |
@chrylis Yes this is considered a workaround until we can get a fix out. The alternative is to use the more verbose workaround I provided. |
@rwinch , @danveloper , what do you think? |
@andirdju Thanks for your thoughts. I think this is quite a bit different:
|
I've been giving this quite a bit of thought and I think the best route is to:
The reason being:
@danveloper I know you disagreed with this approach, so I'd like to encourage a response from you about this. Anyone else following this issue, I'd love to hear your feedback as well. |
@rwinch Is there anything wrong with adding |
@ccit-spence I generally do not like to use a different setup for production than development. The problem is that you are testing your production setup throughout development which means you are almost sure to run into "unforeseen" problems. Given Redis is so easy to setup, I would highly recommend using the same setup in development as production. With all that said, you are obviously free to do as you choose. Using a profile will work, but I wouldn't recommend it for the reasons above. |
@rwinch We basically have 2 sets of testing. One: local for making sure nothing obvious is broken, Two: Test ran on CI servers i.e. production. A problem good or bad is from a dev machine you can't access Elasticache. Elasticache is only accessible via EC2 instances. But yes, I do agree having 2 sets of tests is not something I like to do. In this case since Elasticache is only accessible from EC2 I don't see any other way for a dev to test locally. |
@ccit-spence Are you just using Redis within Elasticache or do you have a custom implementation of Spring Sessions' SessionRepository? If you are just using Redis within Elasticache, you can easily install Redis locally. |
@rwinch Right now it is pretty basic. Just using Spring Session defaults. Main motivation for Elasticache is not having to maintain the cluster/nodes. |
@ccit-spence So you could run a local Redis instance right? Not saying you have to, but it seems like an option that would make the development and production much more similar. Anyways, the choice is totally up to you :) |
I'm reopen this issue because if facing the same issue on https://azure.microsoft.com/en-us/documentation/articles/cache-configure/
|
Thanks! |
I am facing the same issue even after trying the solutions provided above |
The value to set in notify-keyspace-events obbeys to the below information :
This information is located in https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/ParameterGroups.Redis.html#ParameterGroups.Redis.2-8-6 Thanks @danveloper for your explanation. |
When I configured the elasticache (Redis on AWS) using the guide from previous post I fugured out that the |
Did anyone found how to use this with Memorystore for Redis on Google Cloud? It looks like it has the same issue, however it doesn't have any interface to control data :| |
We encountered this issue recently after securing our AWS Redis (ElasticCache) 6.2.6 cluster (running in clustered mode). Adding a Issue
When these failures occur, keyspace events are no longer received. I believe the sporadic nature of these exceptions is due to the potentially asynchronous behavior of PatternSubscriptionTask. Application Details The I learned another aspect of subscribing to keyspace events in a secured Redis cluster is that the application must subscribe to the topic on each node in the cluster. Redis keystore events are not broadcast cluster-wide, and are only broadcast to topics local to that node. We have 9 nodes in our cluster and the application is configured to subscribe to the topic on each node. Could this be causing issues with how Is there a way to disable Spring Session from subscribing to these patterned topics, or disabling Spring Session altogether? Like I mentioned above, we didn't need Spring Session and only included it when we needed to add the Desired Result |
That doesn't sound right - If you're not using Spring Session at all, you need something else to resolve the issue. I'd suggest taking a look at Spring Data Redis reference manual, or opening an issue over there if you don't find anything. |
Redis security recommends disabling the
CONFIG
command so that remote users cannot reconfigure an instance. TheRedisHttpSessionConfiguration
requires access to this during its initialization. Hosted Redis services, like AWS ElastiCache disable this command by default, with no option to re-enable it.The text was updated successfully, but these errors were encountered: