-
Notifications
You must be signed in to change notification settings - Fork 74
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
THREESCALE-8404: Add TLS and ACL support for Redis #3572
Conversation
@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. |
b394b48
to
1da3043
Compare
openshift/system/config/redis.yml
Outdated
password: <%= ENV['REDIS_ACL_PASSWORD'] %> | ||
# == TLS Params == | ||
ssl_params: | ||
ca_file: <%= ENV['REDIS_TLS_CA_FILE'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I commented somewhere on this. I would like first to know how will customers be providing their CA (perhaps depends on operator) before finalizing this variable. To make sure that this is the expected way it should work. At the very least I think we should have a default location to search for this so that users can only mount a secret and then the files are present, porta to use them.
The point about default location applies to the cert and public key variables as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you mentioned that in the Apisonator PR. If not provided, Redis will verify the server certificate against all trusted certificates in the OS default ssl certs folder. In Fedora that's /etc/ssl/certs
.
I would say the ca_path
param would work out of the box (See hiredis-client
) to provide an alternative directory to /etc/ssl/certs
. I can add a new env variable for it, so let the clients use ca_path
or ca_file
as they want.
AFAIK there's no equivalent config param for client certs and keys folder, but I don't think it's really needed, in OpenShift you can mount folders as well as single files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed some changes to allow looking for trusted CA certs through ca_path
. However, you can't just store your cert file under ca_path
, that doesn't work (check redis/redis#7392). You need to manually install your certificate in your OS using the official way. For Fedora, that's update-ca-trust. And, since this mechanism essentially stores the cert in the proper format in the default folder (/etc/ssl/certs
for Fedora), there's no difference between providing or not the ca_path
param.
I also tried setting ca_path
to a directory under my $HOME
which is a symbolic link to /etc/ssl/certs
, and it worked fine.
Summarizing, I don't think ca_path
is much useful but since it's an officially accepted parameter and I already added the code, I think it's OK to provide it, It might be useful to somebody.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are not on the same page here. I'm asking what will be the documented way to mount the certs/key in OpenShift. I know how to create a ca path (sorry for writing ca dir previously) with openssl-rehash
. But probably that wont be the easiest option to mange as an openshift secret. Also the documented way will show what default location to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO if we provide both ca_file
and ca_path
then we are providing all available options for the operator team to define where to place the files or how to mount the secrets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to. Probably filename should be ca_redis.pem
or something like that.
Do we have documented that users can edit this secret?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to. Probably filename should be ca_redis.pem or something like that.
I think ca_cert.pem
is better because this doesn't need to be exclusive for Redis. We provide a way for the user to define in which CAs they want porta to trust, it can be for Redis or anything else in the future. ca_cert.pem
doesn't need to be a single certificate, a certificate bundle is also accepted.
Do we have documented that users can edit this secret?
This is a new feature, not documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, nice. Maybe test that this works, will also help write instructions how to do by customers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the Jira issue description with a "To Document" section
openshift/system/config/redis.yml
Outdated
cert: <%= ENV['REDIS_TLS_CERT'] %> | ||
key: <%= ENV['REDIS_TLS_KEY'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cert: <%= ENV['REDIS_TLS_CERT'] %> | |
key: <%= ENV['REDIS_TLS_KEY'] %> | |
cert: <%= ENV['REDIS_USER_CERT'] %> | |
key: <%= ENV['REDIS_PRIVATE_KEY'] %> |
This naming makes it more clear about the intended purpose of these variables. TLS
is redundant in this case I believe. What else can certificate be about? I'm not super much against the TLS
part but it's nicer without I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the original names (i.e. REDIS_TLS_CERT
and REDIS_TLS_KEY
), because they match how Redis itself calls them, see https://redis.io/docs/management/security/encryption/#running-manually
Also, isn't it called "client certificate" rather than "user"?... But maybe I'm misinterpreting what ssl_params.cert
represents 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they match how Redis itself calls them, see https://redis.io/docs/management/security/encryption/#running-manually
They match the server parameters names, but we are the client here.
Also, isn't it called "client certificate" rather than "user"?... But maybe I'm misinterpreting what
ssl_params.cert
represents 😬
As Porta is the client, cert
is the client certificate.
What about these options?
REDIS_CA_FILE
,REDIS_CERT
andREDIS_KEY
- To match params names
REDIS_CLIENT_CA_FILE
,REDIS_CLIENT_CERT
andREDIS_CLIENT_KEY
- To note it's the client, and match params names
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about these options?
REDIS_CA_FILE
,REDIS_CERT
andREDIS_KEY
- To match params names
REDIS_CLIENT_CA_FILE
,REDIS_CLIENT_CERT
andREDIS_CLIENT_KEY
- To note it's the client, and match params names
I think CLIENT_CA_FILE
is not necessarily accurate, it's not client or server, it's just CA, isn't it? 🙃
REDIS_CA_FILE
and REDIS_CERT
look good, but REDIS_KEY
IMO is too vague.
So, I'd be sticking with the original options. Optionally, _TLS_
could be replaced with _SSL_
, but probably not good, because TLS term kind of supersedes SSL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
CLIENT_CA_FILE
is not necessarily accurate, it's not client or server, it's just CA, isn't it? 🙃
Right!! 😅
REDIS_CA_FILE
andREDIS_CERT
look good, butREDIS_KEY
IMO is too vague.So, I'd be sticking with the original options.
What do you mean? Do you have an example of variable names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? Do you have an example of variable names?
I mean that I wouldn't change what you have now :)
ssl_params:
ca_file: <%= ENV['REDIS_TLS_CA_FILE'] %>
ca_path: <%= ENV['REDIS_TLS_CA_PATH'] %>
cert: <%= ENV['REDIS_TLS_CERT'] %>
key: <%= ENV['REDIS_TLS_KEY'] %>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, "client certificate". For me TLS
is redundant as I said but I can live with it. More important for me is to have CLIENT_CERT
and PRIVATE_KEY
. This makes things clear beyond doubt.
We don't have to follow redis naming conventions rather we better establish a clear configuration naming conventions for porta.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finally set them to
ssl_params:
ca_file: <%= ENV['REDIS_CA_FILE'] %>
ca_path: <%= ENV['REDIS_CA_PATH'] %>
cert: <%= ENV['REDIS_CLIENT_CERT'] %>
key: <%= ENV['REDIS_PRIVATE_KEY'] %>
I hope you both agree on these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with these.
config/examples/redis.yml
Outdated
@@ -5,6 +5,15 @@ base: &default | |||
sentinels: "<%= ENV['REDIS_SENTINEL_HOSTS'] %>" | |||
name: <%= ENV['REDIS_SENTINEL_NAME'] %> | |||
role: <%= ENV['REDIS_SENTINEL_ROLE'] %> | |||
# == ACL Params == | |||
username: <%= ENV['REDIS_USERNAME'] %> | |||
password: <%= ENV['REDIS_PASSWORD'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to wrap username-password, and not sure if any other vars into ""
, as having special characters in the password (following the example in README) seems to be breaking YAML:
Uncaught exception: YAML syntax error occurred while parsing /home/dmayorov/Projects/3scale/porta/config/redis.yml. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Error: (<unknown>): did not find expected comment or line break while scanning a block scalar at line 11 column 13
UPDATE: the culprit seems to be the character >
that I accidentally made part of the password, but still :) users may choose any special characters for their password, and we need to avoid breaking YAML...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it might be even better to #to_json
them. Otherwise a password like my nice password with " sign
will break the quoting. While #to_json
should handle all cases.
Then #presence
should be used on the values to check them as they will never be nil
I assume.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed:
# == ACL Params ==
username: <%= ENV['REDIS_USERNAME']&.to_json %>
password: <%= ENV['REDIS_PASSWORD']&.to_json %>
This works fine for nil
, >
and "
, and I assume any other character.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually nil.to_json
results in null
so I think the &
is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried removing the &
and it still works. So you're right. But I'm still not sure, because:
$ bundle exec irb
irb(main):001:0> nil.to_json
Traceback (most recent call last):
...
NoMethodError (undefined method `to_json' for nil:NilClass)
irb(main):002:0> nil&.to_json
=> nil
But
$ bundle exec rails c
Loading development environment (Rails 6.0.6.1)
[1] pry(main)> nil.to_json
=> "null"
So the &
is only redundant assuming Rails is loaded the moment the yaml is read., which I think we can assume it'll be always true. But I don't like Rails returning a string "null"
, and sending that string to Redis, rather than a plain Ruby nil
which we know for sure it won't cause problems on the Redis gems.
So I'd prefer to keep the &
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a template file which should result in valid YAML which in turn would be loaded by Rails. One option would result in:
field:
The other:
field: null
Both result in equivalent YAML, nothing to do with rails.
🐚 pry
[1] pry(main)> require 'yaml'
=> true
[2] pry(main)> YAML.load "field: "
=> {"field"=>nil}
[3] pry(main)> YAML.load "field: null"
=> {"field"=>nil}
One other concern though is that either way (using &
or not) an empty environment variable will result in the empty string ""
. While your original version would treat and empty value for the variable as nil
. I can't immediately say whether this is a problem or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I removed the &
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI in case it matters, to have the original behavior where unset variable is equivalent to the empty one, we could use ENV['REDIS_PASSWORD'].presence.to_json
Except for the comment I left here: #3572 (comment) everything seems to be working well! Nice 👏 |
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for the renaming comment, all looks great!
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. |
a3f0372
to
bd3df29
Compare
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just a few minor comments.
app/lib/three_scale/redis_config.rb
Outdated
def load_default_ca_cert(conf) | ||
return if conf.dig(:ssl_params, :ca_file).present? || conf.dig(:ssl_params, :ca_path).present? | ||
|
||
cert_path = Rails.root.join('config/ca_cert.pem').to_s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another question. If customer has put this file into the OCP secret, will it end up and be found here automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not automatic unless implemented by the operator. Otherwise the user must edit the yamls to mount the certificate at /opt/system/config
or /opt/system-extra-configs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me that's the whole point of he earlier discussion - when the file is added to the secret, to be used automatically without further configuration. Otherwise it is unnecessary complicated for the users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What could we do from porta?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to coordinate with operator folks how these can be mounted. For CA and cert files, we can put them in config maps I guess and have them mounted to default locations. For private keys though we need to use secrets and idk what is the best way to handle these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to load a default CA cert in the first place? 🤔
I'd say - if nothing is provided in :ca_file
or :ca_path
explicitly, so be it? Does checking config/ca_cert.pem
help in any way?...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's no :ca_file
or :ca_path
it checks config/ca_cert.pem
to load that one. If it doesn't exist, then it loads the system cert bundle in /etc
.
The idea behind config/ca_cert.pem
is to provide a way for the user to use its own certificate effortlessly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I think the effort is pretty much the same in both cases, just that the user will always need to set an env var for :ca_file
or :ca_path
. But at least it's clear where the cert should be mounted (wherever these env vars point to).
I am not sure we need to define some porta-specific CA cert location 🤷
But whatever, if you think it's useful, it's OK for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been discussing with Eguz on how to integrate this TLS anc ACL with the operator, and we came to these two Jira issues:
https://issues.redhat.com/browse/THREESCALE-11020
https://issues.redhat.com/browse/THREESCALE-11021
Basically, porta and apisonator should limit to read the environment variables and it would be the operator who mounts the certificates and set the environment variables according to some new YAML keywords.
I made some changes to adapt to the new approach.
FakeFS.with_fresh do | ||
FakeFS::FileSystem.clone(file_fixture_path) | ||
FakeFS::FileSystem.clone(Rails.root.join('config')) | ||
FileUtils.cp file_fixture('ca_cert.pem'), Rails.root.join('config') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just create an empty file instead of keeping a fixture in the repo
FileUtils.touch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed fcc87b2
FakeFS.with_fresh do | ||
FakeFS::FileSystem.clone(file_fixture_path) | ||
FakeFS::FileSystem.clone(Rails.root.join('config')) | ||
FileUtils.cp file_fixture('ca_cert.pem'), Rails.root.join('config') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just create an empty file instead of keeping a fixture in the repo
FileUtils.touch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed fcc87b2
config/examples/redis.yml
Outdated
username: <%= ENV['REDIS_USERNAME'].to_json %> | ||
password: <%= ENV['REDIS_PASSWORD'].to_json %> | ||
# == TLS Params == | ||
ssl: <%= ENV['REDIS_SSL'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this setting... redis-client
docs say:
ssl
: Whether to connect using SSL or not.
But I am not sure how it is actually used 🤔
If I set it to false
but use rediss://
in the URL, it still connects via SSL. So, is it really needed? 🙃 Or does it only apply when the URL is provided in a different format? e.g. host:port
with no protocol.
Also, if this value is expected to be a boolean, I think we need to cast the env var value (which is considered a string).
I think for most of the boolean values we are using this pattern:
ssl: <%= ENV.fetch('REDIS_SSL', '0') == '1' %>
See settings.yml
So, it needs to be set as REDIS_SSL=1
to be true
.
I don't like it too much, but we have it everywhere. An alternative would be to set REDIS_SSL=true
but then change the config to
ssl: <%= ActiveModel::Type::Boolean.new.cast(ENV['REDIS_SSL']) || false %>
We discussed this with @akostadinov here: #3107 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REDIS_SSL
is optional, because the gem is able to infer it correctly in most cases. But it doesn't infer it correctly for the sentinels + tls scenario. That scenario requires setting it manually.
I'll check the boolean format issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solved parsing REDIS_SSL
3e537ba
@@ -3,8 +3,20 @@ base: &default | |||
size: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %> | |||
pool_timeout: 5 # this is in seconds | |||
sentinels: "<%= ENV['REDIS_SENTINEL_HOSTS'] %>" | |||
sentinel_username: <%= ENV['REDIS_SENTINEL_USERNAME'].to_json %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tested these, but I guess it should just work, as we just pass the config to the redis client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried locally and it works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me with the following configs:
REDIS_SSL=1
REDIS_USERNAME=porta
REDIS_PASSWORD=portapassword
REDIS_CA_FILE=/path/to/ca-root-cert.pem
REDIS_CLIENT_CERT=/path/to/porta.crt
REDIS_PRIVATE_KEY=/path/to/porta.key
REDIS_URL=redis://redis-master
REDIS_SENTINEL_HOSTS=localhost:56380,localhost:56381,localhost:56382
REDIS_SENTINEL_NAME=redis-master
REDIS_SENTINEL_ROLE=master
REDIS_SENTINEL_USERNAME=sentuser
REDIS_SENTINEL_PASSWORD=sentpass
BACKEND_REDIS_SSL=1
BACKEND_REDIS_USERNAME=apisonator
BACKEND_REDIS_PASSWORD=apisonatorpassword
BACKEND_REDIS_CA_FILE=/path/to/ca-root-cert.pem
BACKEND_REDIS_CLIENT_CERT=/path/to/apisonator.crt
BACKEND_REDIS_PRIVATE_KEY=/path/to/apisonator.key
BACKEND_REDIS_URL=redis://redis-master
BACKEND_REDIS_SENTINEL_HOSTS=localhost:56380,localhost:56381,localhost:56382
BACKEND_REDIS_SENTINEL_NAME=redis-master
BACKEND_REDIS_SENTINEL_ROLE=master
BACKEND_REDIS_SENTINEL_USERNAME=sentuser
BACKEND_REDIS_SENTINEL_PASSWORD=sentpass
Great job 👍
ca_file: <%= ENV['BACKEND_REDIS_CA_FILE'] %> | ||
cert: <%= ENV['BACKEND_REDIS_CLIENT_CERT'] %> | ||
key: <%= ENV['BACKEND_REDIS_PRIVATE_KEY'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my assumption is correct, one can enable ssl with just CA when client auth is not needed. So we should still allow cert and key to be omitted.
Also I believe we should allow CA_FILE to be nil
when the default operating system CA is suitable for the connection.
ca_file: <%= ENV['BACKEND_REDIS_CA_FILE'] %> | |
cert: <%= ENV['BACKEND_REDIS_CLIENT_CERT'] %> | |
key: <%= ENV['BACKEND_REDIS_PRIVATE_KEY'] %> | |
ca_file: <%= ENV['BACKEND_REDIS_CA_FILE'].to_json %> | |
cert: <%= ENV['BACKEND_REDIS_CLIENT_CERT'].to_json %> | |
key: <%= ENV['BACKEND_REDIS_PRIVATE_KEY'].to_json %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, they all can be nil
. No problem
ssl_params: | ||
ca_file: <%= ENV['REDIS_CA_FILE'] %> | ||
cert: <%= ENV['REDIS_CLIENT_CERT'] %> | ||
key: <%= ENV['REDIS_PRIVATE_KEY'] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ssl_params: | |
ca_file: <%= ENV['REDIS_CA_FILE'] %> | |
cert: <%= ENV['REDIS_CLIENT_CERT'] %> | |
key: <%= ENV['REDIS_PRIVATE_KEY'] %> | |
ssl_params: | |
ca_file: <%= ENV['REDIS_CA_FILE'].to_json %> | |
cert: <%= ENV['REDIS_CLIENT_CERT'].to_json %> | |
key: <%= ENV['REDIS_PRIVATE_KEY'].to_json %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, ignore this, it will result in nil
anyway, correct? So no need to apply this. Will it handle spaces in path without to_json
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the call to to_json
for anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_json
will escape the values if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's fine if you leave it for another time, but would be really nice to get rid of these openshift specific files, is there anything different than the default config files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's fine if you leave it for another time, but would be really nice to get rid of these openshift specific files, is there anything different than the default config files?
@akostadinov let's cover this in #3766
Once this PR is merged, I'll incorporate the changes there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
username: <%= ENV['REDIS_USERNAME'].to_json %> | ||
password: <%= ENV['REDIS_PASSWORD'].to_json %> | ||
# == TLS Params == | ||
ssl: <%= ENV.fetch('REDIS_SSL', '0') == '1' %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little concerned that one may set CA without updating this value. But maybe unnecessary complication to try to prevent this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that situation the Redis client will infer ssl: true
. This variable is optional in almost all cases, it's only mandatory when connecting to TLS protected sentinels.
Check: redis/redis-rb#1249
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just wondering, when this is set to false
hopefully it wont enforce no SSL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just wondering, when this is set to
false
hopefully it wont enforce no SSL
I believe it will ONLY enable SSL when REDIS_SSL=1
, it will be disabled with any other value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it will ONLY enable SSL when REDIS_SSL=1, it will be disabled with any other value.
True
I'm just wondering, when this is set to false hopefully it wont enforce no SSL
No, you can ignore the value or set it to 0
, false
or whatever and it still connects to redis if you provide a certificate. The scenario you mention would only fail for sentinels + TLS, where REDIS_SSL=1
is mandatory.
To accept `ssl_params` and load a default CA cert if provided
And update example files
1e2d428
to
7a35601
Compare
What this PR does / why we need it:
This implements support for ACL and TLS credentials for Redis from Porta.
After Sidekiq 7 upgrade, this PR has become pretty trivial: It only requires some minimal changes in the Redis config parser class to be able to read SSL params from yaml config files. It also loads a default CA cert from the
/config
folder if one is needed and no other certificate is provided.All other changes are on tests. Also, all yaml example files have been updated.
Which issue(s) this PR fixes
THREESCALE-8404
Verification steps
This requires three steps:
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.
openssl genrsa -out ca-root-key.pem 4096
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
asCN
, if that's the domain you're going to use to connect to Redis:openssl genrsa -out redis.key 4096
openssl req -new -key redis.key -out redis.csr
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: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: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
:backend-redis-yml
: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, thessl_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 theredis-client
gem still haven't added support for verify modes (see: redis-rb/redis-client#133) so we can't setSSL_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:
openssl genrsa -out porta.key 4096
openssl req -new -key porta.key -out porta.csr
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
:backend-redis-yml
: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: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.