How to use encrypted database credentials » History » Version 1
Jan Catrysse, 2024-09-29 12:27
1 | 1 | Jan Catrysse | {{TOC}} |
---|---|---|---|
2 | |||
3 | h1. Securing Sensitive Credentials in Redmine using Rails Encrypted Credentials |
||
4 | |||
5 | Managing sensitive information such as database credentials securely is crucial for any Redmine installation. This guide walks through the process of securing database passwords by leveraging the encrypted credentials feature in Rails. |
||
6 | |||
7 | Starting with **Rails 5.2**, sensitive data (like API keys, database passwords, etc.) can be stored in an encrypted format, ensuring that only your Redmine instance with access to the decryption key can read it. |
||
8 | |||
9 | h2. Steps for Securing Credentials |
||
10 | |||
11 | h3. Generate the Master Key |
||
12 | When you use Rails credentials for the first time, Rails will generate a master key. This key is critical to encrypt and decrypt your credentials. Run the following commands to edit the encrypted credentials file: |
||
13 | |||
14 | <pre><code class="shell"> |
||
15 | sudo su - redmine # Login on your SSH prompt as the Redmine user |
||
16 | cd /var/www/redmine/ # Change to the Redmine home directory |
||
17 | EDITOR="nano" bundle exec rails credentials:edit # Start editing the credentials file using your favorite editor: nano |
||
18 | </code></pre> |
||
19 | |||
20 | Add your database credentials inside the encrypted credentials file: |
||
21 | |||
22 | <pre><code class="shell"> |
||
23 | db: |
||
24 | username: redmine |
||
25 | password: super_secure_password |
||
26 | </code></pre> |
||
27 | |||
28 | h3. Securely Store the Master Key |
||
29 | |||
30 | The @config/master.key@ file must be stored securely and excluded from your version control system. |
||
31 | Ensure it is added to @.gitignore@. Only environments that need to read the encrypted credentials should have access to the master key. |
||
32 | This step is already partialy taken care of by the previous @rails credentials:edit@ command. |
||
33 | |||
34 | <pre><code class="shell"> |
||
35 | echo 'config/master.key' >> .gitignore |
||
36 | echo 'config/credentials.yml.enc' >> .gitignore |
||
37 | </code></pre> |
||
38 | |||
39 | Verify @config/credentials.yml.enc@ and @onfig/master.key@ ownership and permissions. |
||
40 | |||
41 | h3. Modify @database.yml@ to Reference Encrypted Credentials |
||
42 | |||
43 | Now, reference your encrypted credentials in @config/database.yml@: |
||
44 | |||
45 | <pre><code class="shell"> |
||
46 | production: |
||
47 | adapter: postgresql |
||
48 | database: redmine |
||
49 | host: db01.int.geoxyz.eu |
||
50 | username: <%= Rails.application.credentials.dig(:db, :username) %> |
||
51 | password: <%= Rails.application.credentials.dig(:db, :password) %> |
||
52 | sslmode: require |
||
53 | encoding: utf8 |
||
54 | schema_search_path: public |
||
55 | pool: 20 |
||
56 | </code></pre> |
||
57 | |||
58 | This approach ensures that your database passwords are stored securely and can only be decrypted by systems with access to the @master.key@. |
||
59 | |||
60 | h3. Patching the @Gemfile@ |
||
61 | |||
62 | Refer to the Redmine @Gemfile@ patch below that resolves issues when parsing the @database.yml@ file using ERB: #41331 |
||
63 | |||
64 | <pre><code class="diff"> |
||
65 | Index: Gemfile |
||
66 | IDEA additional info: |
||
67 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP |
||
68 | <+>UTF-8 |
||
69 | =================================================================== |
||
70 | diff --git a/Gemfile b/Gemfile |
||
71 | --- a/Gemfile (revision 277728afc979a7123eef8d1a8ac54d74c235c5fc) |
||
72 | +++ b/Gemfile (date 1727590462090) |
||
73 | @@ -61,7 +61,7 @@ |
||
74 | require 'yaml' |
||
75 | database_file = File.join(File.dirname(__FILE__), "config/database.yml") |
||
76 | if File.exist?(database_file) |
||
77 | - yaml_config = ERB.new(IO.read(database_file)).result |
||
78 | + yaml_config = ERB.new(IO.readlines(database_file).reject { |line| line =~ /<%.*%>/ }.join).result |
||
79 | database_config = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml_config) : YAML.load(yaml_config) |
||
80 | adapters = database_config.values.filter_map {|c| c['adapter']}.uniq |
||
81 | if adapters.any? |
||
82 | </code></pre> |