Redis with Envoy

2020-02-03

Cheatsheet to setup Envoy to proxy Redis traffic using TLS and Redis AUTH.

I’m writing this up since i found it really tricky to setup the envoy side of things…especially with both downstream and upstream AUTH:

hope this helps you spend some hours on other things..

What we’re going to do:

Setup a go redis client app to talk via TLS to envoy. Envoy will then proxy requests to Redis server.

Both client->envoy->redis is secured by redis AUTH

client->envoy-->redis uses mTLS end to end. client->redis is on port :6000 while envoy->redis is on port :6379

What you’ll need:


You can find the source here


Setup

Download the source files froe the git repo here. You should end up with:

git clonehttps://github.com/salrashid123/envoy_redis
  • basic.yaml: Envoy config file
  • CA_crt.epm: The CA Cert for mtls
  • server.crt, server.key: Server certs envoy will use
  • client.crt, client.key: client side certs the go app will use
  • main.go: Redis golang client that will connect to Envoy

First edit /etc/hosts and add the following to make it easier for TLS handshake

127.0.0.1 server.domain.com

Redis

Once you donwload redis, edit redis.conf and uncomment the following line to enable default user AUTH:

# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
# layer on top of the new ACL system. The option effect will be just setting
# the password for the default user. Clients will still authenticate using
# AUTH <password> as usually, or more explicitly with AUTH default <password>
# if they follow the new protocol: both will work.
#
requirepass foobared

Start Redis:

redis-server --tls-port 6379 --port 0  \
         --tls-cert-file server.crt    \
         --tls-key-file server.key     \
         --tls-ca-cert-file CA_crt.pem

Envoy

Start Envoy:

envoy  -c basic.yaml  -l debug

The whole reason for this article is because i found it hard to configure enovy…so here it is:

Some things to point out below:

  • Envoy Listens on mTLS: the config tls_context that
  • Clients connecting to envoy must provide a redis password: foo
  • Envoy connect to Redis outbound with mtls: the config transport_socket does that
  • Envoy connects to Redis must provide a redis: foobared

In enovy-speak, the client is downstream while redis is upstream as far as envoy is concerned.

static_resources:
  listeners:
  - name: redis_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 6000

    filter_chains:
    - filters:
      - name: envoy.redis_proxy
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy
          stat_prefix: egress_redis
          settings:
            op_timeout: 5s
          prefix_routes:
            catch_all_route:
              cluster: redis_cluster 
          downstream_auth_password:
            inline_string: "bar"
      tls_context:
        common_tls_context:
          tls_certificates:
          - certificate_chain:
              filename: "server.crt"
            private_key:
              filename: "server.key"
          validation_context:
            trusted_ca:
              filename: CA_crt.pem
  clusters:
  - name: redis_cluster
    connect_timeout: 1s
    type: strict_dns
    load_assignment:
      cluster_name: redis_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 6379
    typed_extension_protocol_options:
      envoy.filters.network.redis_proxy:
        "@type": type.googleapis.com/google.protobuf.Struct
        value:
          auth_password:
            inline_string: "foobared"
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        common_tls_context:
          tls_certificates:
            certificate_chain: { "filename": "client.crt" }
            private_key: { "filename": "client.key" }
          validation_context:
            trusted_ca:
              filename: CA_crt.pem

admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8001

Start go redis client

main.go connects to envoy on port :6000 and presents a client certificate to it:

	caCert, err := ioutil.ReadFile("CA_crt.pem")
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	cer, err := tls.LoadX509KeyPair("client.crt", "client.key")
	if err != nil {
		fmt.Printf("%v", err)
		return
	}

	config := &tls.Config{
		RootCAs:      caCertPool,
		Certificates: []tls.Certificate{cer},
		ServerName:   "server.domain.com",
	}

	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6000", // envoy
		Password: "bar",            //envoy
		//Addr:      "localhost:6379", //direct
		//Password:  "foobared", // direct
		DB:        0, // use default DB
		TLSConfig: config,
	})

	pong, err := client.Ping().Result()
	if err != nil {
		fmt.Printf("Unable to ping %v\n", err)
		return
	}
	fmt.Println(pong, err)

If you run the app now you’ll see a pong and the value you just saved:

$ go run main.go 
PONG <nil>
key value

Note, you can switch the go client between going through enovy or directly to redis by commenting out the sections

	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6000", // envoy
		Password: "bar",            //envoy
		//Addr:      "localhost:6379", //direct
		//Password:  "foobared", // direct
		DB:        0, // use default DB
		TLSConfig: config,
	})

…yeah, thats it..

This site supports webmentions. Send me a mention via this form.