question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ERR unknown command 'CONFIG' when using Secured Redis

See original GitHub issue

Redis security recommends disabling the CONFIG command so that remote users cannot reconfigure an instance. The RedisHttpSessionConfiguration 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.

Issue Analytics

  • State:closed
  • Created 9 years ago
  • Comments:27 (15 by maintainers)

github_iconTop GitHub Comments

16reactions
danvelopercommented, Jan 26, 2015

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™ 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 RedisHttpSessionConfiguration with an implementation that disables the keyspace notifications initializer, and bringing it in through normal configuration means:

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 service

image

Choose the Cache Parameter Groups and click Create Parameter Group

image

Give the new group and name and description and click Create

image

With the new parameter group created, select it and click Edit Parameters

image

Page through the parameters until you find notify-keyspace-events and enter "eA" in the Value field and click Save Changes

image

Choose Cache Clusters from the context navigation and create a new Redis cache cluster

When specifying your cluster detail, choose the newly created parameter group

image

6reactions
rwinchcommented, Apr 15, 2015

Thanks for the report @danveloper! This indeed seems to be a bug with the RedisHttpSessionConfiguration and thus the @EnableRedisHttpSession annotation.

UPDATE Fixing in 1.0.1

As of Spring Session 1.0.1 this can be disabled by exposing ConfigureRedisAction.NO_OP as a bean.

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 Issue

I’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:

  • It makes it very easy to get things up and working in a development environment
  • Making it enabled by default means that users would have to explicitly disable for production. Since there is an explicit step to disable the configuration, they should be aware that it is necessary to configure Redis to send the namespace notifications. This is critical for applications that require SessionDestroyedEvent to be fired to clean up resources. In particular, this is important for WebSocket applications to ensure open WebSockets are closed when the HttpSession expires.

My initial thoughts on how we should update the configuration is:

  • RedisHttpSessionConfiguration should by default update the Redis configuration only if Spring WebSocket support is enabled.
  • RedisHttpSessionConfiguration should allow disabling updating the Redis configuration
  • RedisHttpSessionConfiguration should by default try to subscribe to keyspace notifications only if Spring WebSocket support is enabled. This will help increase performance for applications simply using Spring Session for HttpSession which typically does not need to receive the SessionDestroyedEvent
  • RedisHttpSessionConfiguration should allow explicitly configuring if the application should subscribe to keyspace notifications
  • We should update the documentation to discuss the changes

Workaround

In the meantime, a workaround is to remove @EnableRedisHttpSession from your configuration and then include a configuration with a fix. For example:

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 SessionDestroyedEvent you can also disable subscribing to the notifications which should improve performance. For example:

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;
    }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

ERR unknown command `CONFIG when trying to set dir
I'm trying to run the following redis-cli --raw -a '<password censored>' 'CONFIG SET dir /var/www/html'. However I keep getting: ERR unknown ...
Read more >
Managed Redis configuration - DigitalOcean
when accessing redis via redlii there are no config variables available. e.g. > config get * (error) ERR unknown command `config`, with args ......
Read more >
Why Is "ERR unknown command" Displayed When I Access a ...
Why Is "ERR unknown command" Displayed When I Access a DCS Redis Instance Through a Redis Client? ... The possible causes are as...
Read more >
Azure/AWS: Take into account CONFIG command may not work
Trying to open the status page will throw this (at least running Predis): Predis\Response\ServerException: ERR unknown command `CONFIG`, ...
Read more >
ERR unknown command 'CONFIG' when using Secured Redis
ERR unknown command 'CONFIG' when using Secured Redis ... 这段代码在执行到第一个判断就跳出,不在进行连接配置。解决此问题。 ... 可以设置session过期 ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found