THREESCALE-8404: Add TLS and ACL support for Redis
What this PR does / why we need it:
This implements support for ACL and TLS credentials for Redis from Porta.
Which issue(s) this PR fixes
Verification steps
This requires three steps:
- Generate TLS certificates and keys for Redis.
- Configure Redis to enable ACL and TLS, and use the generated keys.
- Configure Porta. 3.1. TLS Client 3.2. Mutual TLS Client
Generate keys and certificates:
The first thing we need is a certification authority. For that, we need to create its key and certificate.
- key:
openssl genrsa -out ca-root-key.pem 4096 - cert:
openssl req -new -x509 -days 365 -key ca-root-key.pem -out ca-root-cert.pem
Then we'll have to create a key and certificate for the server, and sign it with our CA. Ensure setting localhost as CN, if that's the domain you're going to use to connect to Redis:
- key:
openssl genrsa -out redis.key 4096 - cert request:
openssl req -new -key redis.key -out redis.csr - signed cert:
openssl x509 -req -days 365 -in redis.csr -CA ca-root-cert.pem -CAkey ca-root-key.pem -CAcreateserial -out redis.crt
Configure Redis:
First, we need a Redis configuration file which enables ACL and TLS, defines the users and sets the certificate the server will use and the CA the server will trust. This is the minimal redis.conf for that purpose:
port 6379
tls-port 26379
tls-cert-file /etc/redis.crt
tls-key-file /etc/redis.key
tls-ca-cert-file /etc/ca-root-cert.pem
tls-auth-clients optional
user default off
user porta on >sup3rS3cre1! ~* &* +@all
user apisonator on >secret#Passw0rd ~* &* +@all
Next step is to launch a container that has access to the certificates, keys and Redis config files created above. We create a volume for each file and modify the container start command to reference the configuration file we created. This is how I configured the redis pod in my docker-compose.yml. But the container can be launched by other ways:
redis:
image: redis:6.2-alpine
container_name: redis-compose-ssl
ports:
- "26379:26379"
volumes:
- /home/jlledom/redis.conf:/etc/redis.conf:Z
- /home/jlledom/redis.crt:/etc/redis.crt:z
- /home/jlledom/redis.key:/etc/redis.key:z
- /home/jlledom/ca-root-cert.pem:/etc/ca-root-cert.pem:z
command: [redis-server, /etc/redis.conf]
The :z at the end of the volume definitions is required when using Selinux, for instance in Fedora.
Configure Porta:
Porta can be configured as a regular TLS client, when only the server needs a certificate, or a Mutual TLS Client, where both client and server must provide a certificate.
As TLS Client:
Update our config files to add a new base configuration:
redis-yml:
ssl: &ssl
url: "<%= ENV.fetch('REDIS_URL', 'rediss://localhost:26379/5') %>"
pool_size: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
pool_timeout: 5 # this is in seconds
namespace: "<%= ENV['REDIS_NAMESPACE'] %>"
sentinels: "<%= ENV['REDIS_SENTINEL_HOSTS'] %>"
role: <%= ENV['REDIS_SENTINEL_ROLE'] %>
username: porta
password: sup3rS3cre1!
ssl_params:
ca_file: /home/jlledom/ca-root-cert.pem
backend-redis-yml:
ssl: &ssl
url: "<%= ENV.fetch('BACKEND_REDIS_URL', 'rediss://localhost:26379/6') %>"
pool_size: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
pool_timeout: 5 # this is in seconds
sentinels: "<%= ENV['BACKEND_REDIS_SENTINEL_HOSTS'] %>"
role: <%= ENV['BACKEND_REDIS_SENTINEL_ROLE'] %>
username: porta
password: sup3rS3cre1!
ssl_params:
ca_file: /home/jlledom/ca-root-cert.pem
Note how the ssl_params section is not always required. We are using it here because the server is using a certificate signed by an unknown authority we've just created, so we need to explicitly tell Porta to trust that authority. If the server were using a certificate signed by any of the well-known CAs, the ssl_params section could be omitted.
Another possible situation is when the server is using a self-signed certificate. When this happens, there's no trusted CA to add to ssl_params, so the only way to go is to skip certificate validation. Unfortunately we don't support this yet because the redis-client gem still haven't added support for verify modes (see: https://github.com/redis-rb/redis-client/issues/133) so we can't set SSL_VERIFY_NONE to the client. Anyway this is only useful for development purposes, we'll have to use our own CA for development for the moment.
As Mutual TLS Cliet:
Generate a key and a singed certificate for Porta:
- key:
openssl genrsa -out porta.key 4096 - cert request:
openssl req -new -key porta.key -out porta.csr - signed cert:
openssl x509 -req -days 365 -in porta.csr -CA ca-root-cert.pem -CAkey ca-root-key.pem -CAcreateserial -out porta.crt
Update our config files to add a new base configuration:
redis-yml:
ssl: &ssl
url: "<%= ENV.fetch('REDIS_URL', 'rediss://localhost:26379/5') %>"
pool_size: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
pool_timeout: 5 # this is in seconds
namespace: "<%= ENV['REDIS_NAMESPACE'] %>"
sentinels: "<%= ENV['REDIS_SENTINEL_HOSTS'] %>"
role: <%= ENV['REDIS_SENTINEL_ROLE'] %>
username: porta
password: sup3rS3cre1!
ssl_params:
ca_file: /home/jlledom/ca-root-cert.pem
cert: /home/jlledom/porta.crt
key: /home/jlledom/porta.key
backend-redis-yml:
ssl: &ssl
url: "<%= ENV.fetch('BACKEND_REDIS_URL', 'rediss://localhost:26379/6') %>"
pool_size: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
pool_timeout: 5 # this is in seconds
sentinels: "<%= ENV['BACKEND_REDIS_SENTINEL_HOSTS'] %>"
role: <%= ENV['BACKEND_REDIS_SENTINEL_ROLE'] %>
username: porta
password: sup3rS3cre1!
ssl_params:
ca_file: /home/jlledom/ca-root-cert.pem
cert: /home/jlledom/porta.crt
key: /home/jlledom/porta.key
As mentioned above, the ca_file field is only required when the server isn't using a certificate signed by one of the well-known CAs.
Enable the TLS Client:
You can switch between TLS and non-TLS client by updating the development environment to use the new base, on both files:
development:
<<: *ssl
At this point it should work. You can verify the connection using RedisInsight or trying with invalid certs or keys from the porta side, to verify it works fine.
@akostadinov @mayorova I added some comments
I updated the PR description to add the use case when Porta doesn't provide a certificate.
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
Except for the comment I left here: https://github.com/3scale/porta/pull/3572#discussion_r1372985920 everything seems to be working well! Nice :clap:
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has not received activity for more than 14 days. Remove stale label or comment or this will be closed in 7 days.
This PR is stale because it has not received activity for more than 30 days. Remove stale label or comment or this will be closed in 15 days.
Another possible situation is when the server is using a self-signed certificate. When this happens, there's no trusted CA to add to ssl_params, so the only way to go is to skip certificate validation.
With self-signed certificate, you should be able to use the certificate itself as CA too. Should work OOB or maybe you need also to mark it as CA when creating.