Discussion and recommendation on redis's security free configuration in docker compose

Keywords: Linux Redis Docker CentOS github

In an interesting discussion, let's take a look at the docker compose configuration script:

version: '2'

services:
  redis:
    image: 'redis:5.0.3-stretch'
    restart: always
    command: redis-server --requirepass redis
    environment:
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
      - ALLOW_EMPTY_PASSWORD=yes
      - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
    ports:
      - '6379:6379'
    volumes:
      - 'redis_data:/bitnami/redis/data'

volumes:
  redis_data:
    driver: local

The background of this script is explained here. At the beginning, bitnami version was used, because it supports password free configuration and has great advantages in configuration and development environment. Later, for the sake of security, redis:5.0.3-stretch, the official image file, was used, and then there was such a strange configuration script, including both requirepass and allow? EMP TY_PASSWORD=yes

In this configuration, passwd: redis is configured in commit, but allow ﹣ empty ﹣ password = yes is configured in environment. Do you need a password or not? What's the reason?

To solve this problem, first of all, the problem must not be on docker compose, which is at best a script parser. Usually, the priority of running the specified parameters is greater than the environment variables, and the environment variables are greater than the configuration file, but is this really the case? Then we need to read the redis source code. If you are interested, you can read it by yourself redis source code

It is written in src/config.c that if the command requirepass is used, the content of password will be written into sds, but it seems that the impact of environment on redis configuration file has not been explained, or even the call to environment variable has not been introduced in the whole config file, so it can be inferred that developers should not consider using environment

In order to prove this conjecture, I don't want to find the source code in a haystack, so I'd like to see the official config Introduction

Passing arguments via the command line
Since Redis 2.6 it is possible to also pass Redis configuration parameters using the command line directly. This is very useful for testing purposes. The following is an example that starts a new Redis instance using port 6380 as a slave of the instance running at 127.0.0.1 port 6379.

./redis-server --port 6380 --slaveof 127.0.0.1 6379

The format of the arguments passed via the command line is exactly the same as the one used in the redis.conf file, with the exception that the keyword is prefixed with --.

Note that internally this generates an in-memory temporary config file (possibly concatenating the config file passed by the user if any) where arguments are translated into the format of redis.conf.

Changing Redis configuration while the server is running
It is possible to reconfigure Redis on the fly without stopping and restarting the service, or querying the current configuration programmatically using the special commands CONFIG SET and CONFIG GET

Not all the configuration directives are supported in this way, but most are supported as expected. Please refer to the CONFIG SET and CONFIG GET pages for more information.

Note that modifying the configuration on the fly has no effects on the redis.conf file so at the next restart of Redis the old configuration will be used instead.

Make sure to also modify the redis.conf file accordingly to the configuration you set using CONFIG SET. You can do it manually, or starting with Redis 2.8, you can just use CONFIG REWRITE, which will automatically scan your redis.conf file and update the fields which don't match the current configuration value. Fields non existing but set to the default value are not added. Comments inside your configuration file are retained.

Through the introduction, we can know that there are two ways to configure redis:

  • Through the command line, this method is configured through the command command. It belongs to initialization configuration and will be written to the redis.conf file
  • During the operation, this mode is configured during the operation of redis. It belongs to temporary configuration and will not be written to redis.conf. When the service is restarted, the original configuration will fail. You need to re-enter the instruction config set xxx

At this point, we know that the environment is invalid for the genuine redis, so why does the bitnami version have this effect? At this time, we need to see how this guy designed it.
Let's take a look at people's docker-compose.yml , no secret access is allowed. This is good. It's OK to make a small fuss by yourself. When encountering team assistance or being sprayed in the production environment in minutes

version: '2'

services:
  redis:
    image: 'bitnami/redis:5.0-centos-7'
    environment:
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
      - ALLOW_EMPTY_PASSWORD=yes
      - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
    ports:
      - '6379:6379'
    volumes:
      - 'redis_data:/bitnami/redis/data'

volumes:
  redis_data:
    driver: local

And then it's his analysis dockerfile , the packaging content in the front is really not good-looking, mainly to see the last two steps, entrypoint ["/ entrypoint. Sh"] & & CMD ["/ run. Sh"], which is the key to allow password free configuration. We will continue to explore the source code

FROM bitnami/centos-extras-base:7-r269
LABEL maintainer "Bitnami <containers@bitnami.com>"

ENV BITNAMI_PKG_CHMOD="-R g+rwX" \
    HOME="/" \
    OS_ARCH="x86_64" \
    OS_FLAVOUR="centos-7" \
    OS_NAME="linux"

# Install required system packages and dependencies
RUN install_packages glibc
RUN . ./libcomponent.sh && component_unpack "redis" "5.0.7-0" --checksum 0046ebee1870e41fe422f646d504a8ec84efb85152189ee434d8f4c9ad2917c7

COPY rootfs /
RUN /postunpack.sh
ENV BITNAMI_APP_NAME="redis" \
    BITNAMI_IMAGE_VERSION="5.0.7-centos-7-r59" \
    NAMI_PREFIX="/.nami" \
    PATH="/opt/bitnami/redis/bin:$PATH"

EXPOSE 6379

USER 1001
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "/run.sh" ]

In fact, the most important thing is the file entrypoint.sh, because run.sh is actually used for startup and initialization. Interested readers can go in and read the source code, which will not be covered here. Let's take a look at entrypoint.sh Source code

#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail
#set -o xtrace
# shellcheck disable=SC1091

# Load libraries
. /libbitnami.sh
. /libredis.sh

# Load Redis environment variables
eval "$(redis_env)"

print_welcome_page

if [[ "$*" = *"/run.sh"* ]]; then
    info "** Starting Redis setup **"
    /setup.sh
    info "** Redis setup finished! **"
fi

echo ""
exec "$@"

It is known from the code that the program will execute libbitnami.sh and libredis.sh to complete the configuration of the dependency library, and then use the eval function to extract the data, and then start redis through the run.sh script. The answer to that question is getting closer and closer:), first find libredis.sh and read the code first

In this code, you can search the keyword allow? Empty? Password to determine whether there is a script. We need to configure the script, which is actually found by us

This function realizes the effect of redis registration without password

redis_validate() {
    debug "Validating settings in REDIS_* env vars.."
    local error_code=0

    # Auxiliary functions
    print_validation_error() {
        error "$1"
        error_code=1
    }

    empty_password_enabled_warn() {
        warn "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD}. For safety reasons, do not use this flag in a production environment."
    }
    empty_password_error() {
        print_validation_error "The $1 environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development."
    }

    if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then
        empty_password_enabled_warn
    else
        [[ -z "$REDIS_PASSWORD" ]] && empty_password_error REDIS_PASSWORD
    fi
    if [[ -n "$REDIS_REPLICATION_MODE" ]]; then
        if [[ "$REDIS_REPLICATION_MODE" =~ ^(slave|replica)$ ]]; then
            if [[ -n "$REDIS_MASTER_PORT_NUMBER" ]]; then
                if ! err=$(validate_port "$REDIS_MASTER_PORT_NUMBER"); then
                    print_validation_error "An invalid port was specified in the environment variable REDIS_MASTER_PORT_NUMBER: $err"
                fi
            fi
            if ! is_boolean_yes "$ALLOW_EMPTY_PASSWORD" && [[ -z "$REDIS_MASTER_PASSWORD" ]]; then
                empty_password_error REDIS_MASTER_PASSWORD
            fi
        elif [[ "$REDIS_REPLICATION_MODE" != "master" ]]; then
            print_validation_error "Invalid replication mode. Available options are 'master/replica'"
        fi
    fi

    [[ "$error_code" -eq 0 ]] || exit "$error_code"
}

It's really interesting to explore the source code. Let's get to the bottom of the matter, face the source, and know the bad consequences of copying without thinking before. In the original docker compose configuration, after using the version of 5.0.3-stretch, the text of environment is actually invalid. So far, the problem is solved

reference material:

  1. redis official introduction to config, https://redis.io/topics/config
  2. redis source code, https://github.com/antirez/redis/
  3. docker-compose redis: bitnami,https://github.com/bitnami/bitnami-docker-redis

Posted by jassikundi on Thu, 16 Jan 2020 19:23:52 -0800