From c0a1fbb7e8d5ca5fc7b6f28647e033a1348d4a52 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Sat, 30 Nov 2024 09:45:07 -0300 Subject: [PATCH 1/7] Upgrade Ruby version to 3.3 as Redmine 6.0 allows --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index de2cfe63..94783c00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.2-slim-bookworm +FROM ruby:3.3-slim-bookworm LABEL maintainer="sameer@damagehead.com" From 474677ef3f0695ddcdd307c68d0759a7c54c1d2b Mon Sep 17 00:00:00 2001 From: Abinoam Praxedes Marques Junior Date: Sun, 24 Nov 2024 21:11:36 +0000 Subject: [PATCH 2/7] Add docker compose for aws secrets --- docker-compose-aws.yml | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docker-compose-aws.yml diff --git a/docker-compose-aws.yml b/docker-compose-aws.yml new file mode 100644 index 00000000..be3951a2 --- /dev/null +++ b/docker-compose-aws.yml @@ -0,0 +1,49 @@ +services: + redmine: + build: ./ + image: sameersbn/redmine:6.0.1 + environment: + - TZ=America/Sao_Paulo + - AWS_DB_CREDENTIALS_SECRET_REGION= + - AWS_DB_CREDENTIALS_SECRET_NAME= + - DB_NAME=redmine + - DB_SSL_MODE=prefer + - DB_CREATE=false + + - REDMINE_PORT=10083 + - REDMINE_HTTPS=false + - REDMINE_RELATIVE_URL_ROOT= + - REDMINE_SECRET_TOKEN= + + - REDMINE_SUDO_MODE_ENABLED=false + - REDMINE_SUDO_MODE_TIMEOUT=15 + + - REDMINE_CONCURRENT_UPLOADS=2 + + - REDMINE_BACKUP_SCHEDULE= + - REDMINE_BACKUP_EXPIRY= + - REDMINE_BACKUP_TIME= + + - SMTP_ENABLED=false + - SMTP_METHOD=smtp + - SMTP_DOMAIN=www.example.com + - SMTP_HOST=smtp.gmail.com + - SMTP_PORT=587 + - SMTP_USER=mailer@example.com + - SMTP_PASS=password + - SMTP_STARTTLS=true + - SMTP_AUTHENTICATION=:login + + - IMAP_ENABLED=false + - IMAP_HOST=imap.gmail.com + - IMAP_PORT=993 + - IMAP_USER=mailer@example.com + - IMAP_PASS=password + - IMAP_SSL=true + - IMAP_INTERVAL=30 + + ports: + - "10083:80" + volumes: + - /srv/docker/redmine/redmine:/home/redmine/data + - /srv/docker/redmine/redmine-logs:/var/log/redmine From 679fd5f96e418ee0b779e40d6db70d88df45b299 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Sun, 24 Nov 2024 22:40:55 -0300 Subject: [PATCH 3/7] Add Ruby script to fetch AWS DB credentials secret --- assets/runtime/get_aws_secret.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 assets/runtime/get_aws_secret.rb diff --git a/assets/runtime/get_aws_secret.rb b/assets/runtime/get_aws_secret.rb new file mode 100644 index 00000000..34cf4f23 --- /dev/null +++ b/assets/runtime/get_aws_secret.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +require 'aws-sdk-secretsmanager' + +# Secret name in AWS Secrets Manager +secret_name = ARGV[0] +region_name = ARGV[1] + +raise "Usage: #{__FILE__} " unless ARGV[0] && ARGV[1] + +# Retrieve credentials from Secrets Manager +begin + client = Aws::SecretsManager::Client.new(region: region_name) + secret_value = client.get_secret_value(secret_id: secret_name) + + # Extract the credentials + puts secret_value.secret_string +rescue Aws::SecretsManager::Errors::ServiceError => e + puts "Error accessing Secrets Manager: #{e.message}" + exit 1 +end From 1e1cba4908ebf9a7c6626d2b0b3c6d9c44dcd35d Mon Sep 17 00:00:00 2001 From: Abinoam Praxedes Marques Junior Date: Mon, 25 Nov 2024 01:21:07 +0000 Subject: [PATCH 4/7] Install jq in container Used for parsing json output from get_aws_secret.rb --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 94783c00..6153658a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ imagemagick subversion git cvs bzr mercurial darcs rsync locales openssh-client \ gcc g++ make patch pkg-config gettext-base libc6-dev zlib1g-dev libxml2-dev \ default-libmysqlclient-dev libmariadb-dev libpq5 libyaml-0-2 libcurl4 libssl3 uuid-dev xz-utils \ - libxslt1.1 libffi8 zlib1g gsfonts vim-tiny ghostscript sqlite3 libsqlite3-dev \ + libxslt1.1 libffi8 zlib1g gsfonts vim-tiny ghostscript sqlite3 libsqlite3-dev jq\ && update-locale LANG=C.UTF-8 LC_MESSAGES=POSIX \ && gem install --no-document bundler \ && rm -rf /var/lib/apt/lists/* From d8526c7c649bbd22524dee455c395a2bcbef8f63 Mon Sep 17 00:00:00 2001 From: Abinoam Praxedes Marques Junior Date: Mon, 25 Nov 2024 01:21:29 +0000 Subject: [PATCH 5/7] Check for AWS env vars and configure database.yml --- assets/runtime/functions | 44 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/assets/runtime/functions b/assets/runtime/functions index 922ba4f8..255ab2e8 100644 --- a/assets/runtime/functions +++ b/assets/runtime/functions @@ -104,10 +104,50 @@ update_template() { } redmine_finalize_database_parameters() { + if [[ -n ${AWS_DB_CREDENTIALS_SECRET_NAME} ]]; then + # Set the region, prioritizing a specific value if available + local REGION=${AWS_DB_CREDENTIALS_SECRET_REGION:-${AWS_REGION}} + + echo "Installing aws-secrets-manager Ruby gem..." + gem install aws-secrets-manager --no-document --silent + + echo "Fetching credentials from AWS Secrets Manager using get_aws_secret.rb..." + local SECRET_STRING=$(ruby ${REDMINE_RUNTIME_ASSETS_DIR}/get_aws_secret.rb $AWS_DB_CREDENTIALS_SECRET_NAME $REGION) + + # Check if the command was successful + if [[ $? -ne 0 || -z "${SECRET_STRING}" ]]; then + echo "Failed to retrieve credentials from Secrets Manager. Please ensure that permissions and settings are correctly configured." + return 1 + fi + + # Parse the JSON from the SecretString + echo "Parsing received credentials..." + DB_USER=$(echo "${SECRET_STRING}" | jq -r '.username') + DB_PASS=$(echo "${SECRET_STRING}" | jq -r '.password') + DB_ADAPTER=$(echo "${SECRET_STRING}" | jq -r '.engine') + DB_HOST=$(echo "${SECRET_STRING}" | jq -r '.host') + DB_PORT=$(echo "${SECRET_STRING}" | jq -r '.port') + + # Set default encoding and adapter based on the engine + case ${DB_ADAPTER} in + postgres | postgresql) + DB_ADAPTER=postgresql + DB_ENCODING=${DB_ENCODING:-unicode} + ;; + mysql | mysql2) + DB_ADAPTER=mysql2 + DB_ENCODING=${DB_ENCODING:-utf8} + ;; + *) + echo "Error: Unsupported engine '${DB_ADAPTER}'. Only 'postgresql' and 'mysql2' are accepted. Aborting..." + return 1 + ;; + esac + # is a mysql or postgresql database linked? # requires that the mysql or postgresql containers have exposed # port 3306 and 5432 respectively. - if [[ -n ${MYSQL_PORT_3306_TCP_ADDR} ]]; then + elif [[ -n ${MYSQL_PORT_3306_TCP_ADDR} ]]; then DB_ADAPTER=${DB_ADAPTER:-mysql2} DB_HOST=${DB_HOST:-mysql} DB_PORT=${DB_PORT:-${MYSQL_PORT_3306_TCP_PORT}} @@ -286,7 +326,7 @@ redmine_check_mysql_database_tx_isolation() { } redmine_configure_database() { - echo -n "Configuring redmine::database" + echo "Configuring redmine::database" redmine_finalize_database_parameters redmine_check_database_connection From 7cbdcf59181c67d66784996210afd27bab6d74f6 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Sat, 30 Nov 2024 16:21:51 -0300 Subject: [PATCH 6/7] Move DB_SSL_MODE logic to finalize_database_parameters The main reason of this is for aws template. We get the database configuration (and credentials) remotely. So, at the time env-defaults is called, the adapter is not defined. --- assets/runtime/env-defaults | 10 ---------- assets/runtime/functions | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/assets/runtime/env-defaults b/assets/runtime/env-defaults index bae0ce41..483c188b 100644 --- a/assets/runtime/env-defaults +++ b/assets/runtime/env-defaults @@ -65,16 +65,6 @@ case ${DB_TYPE} in sqlite3) DB_ADAPTER=${DB_ADAPTER:-sqlite3} ;; esac -if [[ ! -z "${DB_SSL_MODE}" ]] -then - # Add sslmode or ssl_mode depending on type - case ${DB_ADAPTER} in - mysql2) DB_SSL_MODE="ssl_mode: ${DB_SSL_MODE}" ;; - postgresql) DB_SSL_MODE="sslmode: ${DB_SSL_MODE}" ;; - sqlite3) DB_SSL_MODE= ;; # No ssl - esac -fi - ## UNICORN (deprecated) UNICORN_WORKERS=${UNICORN_WORKERS:-2} UNICORN_TIMEOUT=${UNICORN_TIMEOUT:-60} diff --git a/assets/runtime/functions b/assets/runtime/functions index 255ab2e8..26bea45a 100644 --- a/assets/runtime/functions +++ b/assets/runtime/functions @@ -251,6 +251,16 @@ redmine_finalize_database_parameters() { ;; esac + if [[ ! -z "${DB_SSL_MODE}" ]] + then + # Add sslmode or ssl_mode depending on type + case ${DB_ADAPTER} in + mysql2) DB_SSL_MODE="ssl_mode: ${DB_SSL_MODE}" ;; + postgresql) DB_SSL_MODE="sslmode: ${DB_SSL_MODE}" ;; + sqlite3) DB_SSL_MODE= ;; # No ssl + esac + fi + # set default user and database DB_USER=${DB_USER:-root} DB_NAME=${DB_NAME:-redmine_production} From cfe24b86bb62fc29760e8c6bd746ece1d9130063 Mon Sep 17 00:00:00 2001 From: "Abinoam Praxedes Marques Jr." Date: Sat, 30 Nov 2024 17:48:58 -0300 Subject: [PATCH 7/7] Update README.md and add docs/aws.md for aws feature --- README.md | 5 +++ docs/aws.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/aws.md diff --git a/README.md b/README.md index ff60b756..6d8eb6cd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [PostgreSQL](#postgresql) - [External PostgreSQL Server](#external-postgresql-server) - [Linking to PostgreSQL Container](#linking-to-postgresql-container) + - [AWS RDS Integration](#aws-rds-integration) - [Memcached (Optional)](#memcached-optional) - [External Memcached Server](#external-memcached-server) - [Linking to Memcached Container](#linking-to-memcached-container) @@ -377,6 +378,10 @@ Here the image will also automatically fetch the `DB_NAME`, `DB_USER` and `DB_PA - [orchardup/postgresql](https://hub.docker.com/r/orchardup/postgresql/) - [paintedfox/postgresql](https://hub.docker.com/r/paintedfox/postgresql/) +### AWS RDS Integration +**docker-redmine** has support for fetching secrets from AWS Secrets Manager at runtime. +Read [docs/aws.md](docs/aws.md) for detailed instructions. + ## Memcached (Optional) This image can (optionally) be configured to use a memcached server to speed up Redmine. This is particularly useful when you have a large number users. diff --git a/docs/aws.md b/docs/aws.md new file mode 100644 index 00000000..fb499415 --- /dev/null +++ b/docs/aws.md @@ -0,0 +1,109 @@ +# AWS RDS Integration + +This feature allows Redmine to connect to an AWS RDS PostgreSQL database using credentials dynamically retrieved from AWS Secrets Manager. + + +## **Configuration** + +Use [docker-compose-aws.yml](../docker-compose-aws.yml) as a starting point and set the following environment variables under the `redmine` service: + +```yaml +services: + redmine: + environment: + - AWS_DB_CREDENTIALS_SECRET_REGION= + - AWS_DB_CREDENTIALS_SECRET_NAME= + - DB_NAME=redmine + - DB_CREATE=false +``` + +- **`AWS_DB_CREDENTIALS_SECRET_REGION`**: Specify the AWS region where the secret is stored (e.g., `us-east-1`). +- **`AWS_DB_CREDENTIALS_SECRET_NAME`**: Provide the name or ARN of the secret containing the database credentials. +- **`DB_NAME`**: Specifies the database name. +- **`DB_CREATE`**: Prevents Redmine from trying to create or overwrite the database during startup. + +**Note**: +Unlike other setups, there is no PostgreSQL companion container running alongside Redmine. +You will connect directly to an AWS RDS PostgreSQL instance. + +## **AWS Secrets Manager Setup** + +To store the database credentials securely, create a secret in AWS Secrets Manager. +Use the following steps: + +1. Log in to the **AWS Management Console** and navigate to **Secrets Manager**. +2. Click **Store a new secret** and select **Credentials for Amazon RDS database** as the secret type. +3. Enter your database credentials (username and password). +4. Select your RDS database instance from the list. +5. Configure the secret name (e.g., `redmine_aws_rds_credentials`) and save the secret. + +If you selected "Credentials for Amazon RDS database" the secret will have the following fields, at least: +* engine (it will be translated into "adapter" (eg: postgresql, mysql)) +* username +* password +* host +* port + +## **Database Preparation** + +Before starting the container: + +1. Create the PostgreSQL role and database on your RDS instance manually. Use the same process as for a PostgreSQL companion container as in [External PostgreSQL Server](#external-postgresql-server): + + ```sql + CREATE ROLE redmine with LOGIN CREATEDB PASSWORD 'password'; + CREATE DATABASE redmine_production; + GRANT ALL PRIVILEGES ON DATABASE redmine_production to redmine; + ``` + +2. Ensure the docker-compose.yml file `DB_CREATE=false` environment variable is set to avoid Redmine overwriting the database. + +## **IAM Permissions and EC2 Configuration** + +Ensure that: +1. The EC2 instance running the Redmine container has the appropriate IAM role attached. This role should grant: + - Access to read the secret from AWS Secrets Manager. + - Network access to the RDS instance. + + Example IAM policy for accessing the secret: + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "secretsmanager:GetSecretValue", + "Resource": "arn:aws:secretsmanager:::secret:" + } + ] + } + ``` + +2. Security group rules are configured to allow connections from the EC2 instance to the RDS instance. + +## **How It Works** + +When the container starts: +1. It fetches the database credentials from AWS Secrets Manager using the provided `AWS_DB_CREDENTIALS_SECRET_REGION` and `AWS_DB_CREDENTIALS_SECRET_NAME`. +2. These credentials are used to establish a connection to the AWS RDS PostgreSQL instance. + +This approach eliminates hardcoding sensitive credentials in the `docker-compose.yml` file, enhancing security and flexibility. + +Note: Although the credentials are not hardcoded in the `docker-compose.yml` file, during runtime, they will be written to the config/database.yml file inside the container, just like in the other docker-compose template examples provided by this image. + +## **Debugging** + +You can check if your AWS EC2 instance is properly configured to have access to get the secrets by using the same script used inside the container. + +```bash +gem install aws-sdk-secretsmanager +ruby assets/runtime/get_aws_secret.rb secret_name us-east-1 +``` + +With the received credentials you may try to connect to AWS RDS instance and see if the AWS EC2 instance is allowed to do so. + +```bash +psql -h host -U username -d database +``` + +Check AWS official documentation for more detailed information.