diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7be6b09 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# WordPress variables +WP_VERSION=6.3.1 + +# Setup WordPress variables +WP_ADMIN_EMAIL= +WP_ADMIN_USER= +WP_ADMIN_PASS= + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..565dc58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + +# Local configuration +.env + +#SSH keys +sysadmin-ssh-keys/rsa_sysadmin* +sysadmin-ssh-keys/authorized_keys diff --git a/README.md b/README.md index 95d60dd..b721c43 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,16 @@ See [`CONTRIBUTING.md`][org-contrib]. The aim of the project is to establish a robust and localized development environment utilizing Ansible and Docker. This environment will mirror a professional work setting, incorporating a dedicated security server (Bastion), automation through Ansible, a web server, and a data storage server. This configuration will simplify and secure development processes and serve as a blueprint for future projects at CC. -Docker containers: -- Bastion (SSH jump server) -- Ansible -- Web server (Apache2/WordPress) -- Database server (MariaDB) +#### Docker containers: + +The [`docker-compose.yml`](docker-compose.yml) file defines the following +containers: + +- WIP: Bastion (SSH jump server) +- **ansible-dev** - Ansible +- **web-dev** - Web server (Apache2/WordPress) +- **db-dev** - Database server (MariaDB) ![image](https://github.com/creativecommons/ansible-dev/assets/90766122/21baa18d-715e-4908-9620-15c768994011) @@ -40,6 +44,51 @@ Docker containers: See [Create Local Ansible Dev Environment Using Docker](https://opensource.creativecommons.org/programs/project-ideas/#ansible-dev-env) for more details. +### Setup + +- Create the `.env` file: + ```shell + cp .env.example .env + ``` + +- Execute the `generate_ssh_keys` script: + ```shell + ./generate_ssh_keys.sh + ``` + +- Build and start Docker: + ```shell + docker-compose up + ``` + +- Wait for the build and initialization to complete + + +#### SSH (Work in Progress) + +The SSH setup has been established and is currently in use for the Ansible container. Follow the steps below to generate and use the SSH keys for the sysadmin user: + +- Execute the generate-ssh-keys.sh script to generate the keys used by the sysadmin user: + ```shell + ./generate-ssh-keys.sh + ``` + +- Bring down the existing Docker containers and start them again: + ```shell + docker-compose down + docker-compose up -d + ``` + +- Ensure the Docker containers are running: + ```shell + docker ps + ``` + +- Execute the following command to confirm that SSH is working fine: + ```shell + ssh -i ./sysadmin-ssh-keys/rsa_sysadmin -p 22001 sysadmin@localhost + ``` + ## Related Links - [Ansible Documentation](https://docs.ansible.com/) - [FrontPage - Debian Wiki](https://wiki.debian.org/FrontPage) diff --git a/ansible/Dockerfile b/ansible/Dockerfile index 010546b..463da18 100644 --- a/ansible/Dockerfile +++ b/ansible/Dockerfile @@ -1,5 +1,4 @@ # https://docs.docker.com/engine/reference/builder/ - # https://hub.docker.com/_/debian FROM debian:bookworm-slim @@ -9,7 +8,7 @@ ARG DEBIAN_FRONTEND=noninteractive # Configure apt to avoid installing recommended and suggested packages RUN apt-config dump \ | grep -E '^APT::Install-(Recommends|Suggests)' \ - | sed -e's/1/0/' \ + | sed -e 's/1/0/' \ | tee /etc/apt/apt.conf.d/99no-recommends-no-suggests # Resynchronize the package index files from their sources @@ -20,26 +19,41 @@ RUN apt-get install -y \ python3 \ python3-pip \ python3-venv \ - openssh-client + openssh-client \ + openssh-server \ + wget \ + vim \ + sudo + +# Clean up packages: Saves space by removing unnecessary package files and lists +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +# Create sysadmin user and add to sudoers +RUN useradd -m -s /bin/bash sysadmin && echo "sysadmin:sysadmin" | chpasswd && \ + usermod -aG sudo sysadmin + +# Ensure SSH directory exists with correct permissions +RUN mkdir -p /home/sysadmin/.ssh && \ + chown sysadmin:sysadmin /home/sysadmin/.ssh && \ + chmod 700 /home/sysadmin/.ssh -# Clean up packages: Saves space by removing unnecessary package files -# and lists -RUN apt-get clean -RUN rm -rf /var/lib/apt/lists/* +# Create privilege separation directory for SSH +RUN mkdir -p /run/sshd -# Create a virtual env and install ansible using pip -RUN python3 -m venv /opt/ansible-venv --system-site-packages && \ -/opt/ansible-venv/bin/pip install --no-cache-dir ansible +# Create a virtual environment and install Ansible using pip +RUN python3 -m venv /opt/ansible-venv --system-site-packages && \ + /opt/ansible-venv/bin/pip install --no-cache-dir ansible # Create a directory for Ansible configuration RUN mkdir /etc/ansible/ -# Copy local configuration files to the image -COPY ../config/ /etc/ansible/ - # Set environment variables for Ansible -ENV PATH="/ansible-venv/bin:$PATH" +ENV PATH="/opt/ansible-venv/bin:$PATH" ENV ANSIBLE_CONFIG=/etc/ansible/ansible.cfg -# Set the default command to run Ansible -CMD ["ansible", "--version"] +# Expose SSH port +EXPOSE 22 + +# Start SSH service +CMD ["/usr/sbin/sshd", "-D"] + diff --git a/config/ansible.cfg b/ansible/etc-ansible-config/ansible.cfg similarity index 80% rename from config/ansible.cfg rename to ansible/etc-ansible-config/ansible.cfg index be9c6a9..33702b3 100644 --- a/config/ansible.cfg +++ b/ansible/etc-ansible-config/ansible.cfg @@ -1,5 +1,5 @@ [defaults] inventory = /etc/ansible/hosts -remote_user = root +remote_user = sysadmin host_key_checking = False retry_files_enabled = False diff --git a/ansible/etc-ansible-config/hosts b/ansible/etc-ansible-config/hosts new file mode 100644 index 0000000..192c1d3 --- /dev/null +++ b/ansible/etc-ansible-config/hosts @@ -0,0 +1,9 @@ +[local] +localhost ansible_connection=local + +[web] +web-dev + +[db] +db-dev + diff --git a/config/hosts b/config/hosts deleted file mode 100644 index 7bf7398..0000000 --- a/config/hosts +++ /dev/null @@ -1,2 +0,0 @@ -[local] -localhost ansible_connection=local \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index deae20a..cf5aed9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,91 @@ # https://docs.docker.com/compose/compose-file/ services: - ansible-dev: - container_name: ansible + container_name: ansible-dev build: context: . dockerfile: ansible/Dockerfile + networks: + - dev-backend + volumes: + - ./ansible/etc-ansible-config:/etc/ansible/ + - ./sysadmin-ssh-keys/rsa_sysadmin:/home/sysadmin/.ssh/id_rsa:ro + - ./sysadmin-ssh-keys/rsa_sysadmin.pub:/home/sysadmin/.ssh/id_rsa.pub:ro + - ./sysadmin-ssh-keys/rsa_sysadmin.pub:/home/sysadmin/.ssh/authorized_keys:ro + ports: + - "22001:22" + environment: + - USER=sysadmin + entrypoint: | + sh -c " + exec /usr/sbin/sshd -D + " + + web-dev: + container_name: web-dev + depends_on: + - db-dev + build: + args: + WP_VERSION: ${WP_VERSION:?have you copied .env.example to .env?} + context: . + dockerfile: web/Dockerfile + networks: + - dev-backend + environment: + MYSQL_ROOT_PASSWORD: root + PMA_HOST: db-dev + PMA_PORT: 3306 + WORDPRESS_CONFIG_EXTRA: | + # Use dispatch port by default + if ('${CODESPACE_NAME:-}') { + define('WP_HOME', 'https://${CODESPACE_NAME:-}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:-}'); + } else { + define('WP_HOME', 'http://localhost:8080'); + define('WP_SITEURL', 'http://localhost:8080'); + } + WORDPRESS_DB_HOST: db-dev:3306 + WORDPRESS_DB_PASSWORD: root + WORDPRESS_DB_USER: root + WORDPRESS_DB_NAME: wordpress + WORDPRESS_USER: root + init: true + ports: + - '8080:80' + - '22002:22' + restart: on-failure volumes: - - ./config/ansible.cfg:/etc/ansible/ansible.cfg - - ./config/hosts:/etc/ansible/hosts - command: sh -c 'trap "exit" TERM; while true; do sleep 1; done' \ No newline at end of file + - ./web/config-web/etc-apache2-sites-available:/etc/apache2/sites-available:ro + - ../cc-legal-tools-data:/var/www/git/cc-legal-tools-data:ro + - ../chooser:/var/www/git/chooser:ro + - ../faq:/var/www/git/faq:ro + - ../mp:/var/www/git/mp:ro + - wp-data:/var/www/dev + - ./sysadmin-ssh-keys/rsa_sysadmin:/home/sysadmin/.ssh/id_rsa:ro + - ./sysadmin-ssh-keys/rsa_sysadmin.pub:/home/sysadmin/.ssh/id_rsa.pub:ro + - ./sysadmin-ssh-keys/rsa_sysadmin.pub:/home/sysadmin/.ssh/authorized_keys:ro + + db-dev: + container_name: db-dev + environment: + MYSQL_DATABASE: wordpress + MYSQL_ROOT_PASSWORD: root + MYSQL_USER: root + image: mariadb + networks: + - dev-backend + restart: on-failure + volumes: + - db-data:/var/lib/mysql + +volumes: + db-data: + name: db-data + wp-data: + name: wp-data + +networks: + dev-backend: + name: dev-backend + diff --git a/generate_ssh_keys.sh b/generate_ssh_keys.sh new file mode 100755 index 0000000..532d12b --- /dev/null +++ b/generate_ssh_keys.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Define key parameters +KEY_PATH=./sysadmin-ssh-keys +KEY_FILENAME=rsa_sysadmin + +# Create a directory to store the SSH keys if it doesn't exist +mkdir -p ${KEY_PATH} + +# Generate SSH key pair only if they don't already exist +if [ ! -f ${KEY_PATH}/${KEY_FILENAME} ] +then + ssh-keygen -b 4096 -t rsa -C sysadmin -f ${KEY_PATH}/${KEY_FILENAME} -N '' + echo "SSH keys generated and stored in ${KEY_PATH}/${KEY_FILENAME}" +else + echo "SSH keys already exist in ${KEY_PATH}/${KEY_FILENAME}" +fi + diff --git a/sysadmin-ssh-keys/README.md b/sysadmin-ssh-keys/README.md new file mode 100644 index 0000000..f8e6d44 --- /dev/null +++ b/sysadmin-ssh-keys/README.md @@ -0,0 +1,2 @@ +This directory contains the SSH keys used for the sysadmin user. + diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..cafcfb5 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,124 @@ +# https://docs.docker.com/engine/reference/builder/ +# https://hub.docker.com/_/debian +FROM debian:bookworm-slim + +# Configure apt not to prompt during docker build +ARG DEBIAN_FRONTEND=noninteractive + +# Configure apt to avoid installing recommended and suggested packages +RUN apt-config dump \ + | grep -E '^APT::Install-(Recommends|Suggests)' \ + | sed -e 's/1/0/' \ + | tee /etc/apt/apt.conf.d/99no-recommends-no-suggests + +# Resynchronize the package index files from their sources +RUN apt-get update + +# Install packages +RUN apt-get install -y \ + apache2 \ + apache2-utils \ + ca-certificates \ + curl \ + git \ + less \ + libapache2-mod-php \ + mariadb-client \ + php8.2 \ + php8.2-mbstring \ + php8.2-mysql \ + php8.2-pdo \ + php8.2-xml \ + sudo \ + unzip \ + vim \ + wget \ + openssh-client \ + openssh-server \ + && update-ca-certificates + +# Clean up packages: Saves space by removing unnecessary package files and lists +RUN apt-get clean +RUN rm -rf /var/lib/apt/lists/* + +# Create sysadmin user and add to sudoers +RUN useradd -m -s /bin/bash sysadmin && \ + echo "sysadmin:sysadmin" | chpasswd && \ + usermod -aG sudo sysadmin + +# Ensure SSH directory exists with correct permissions +RUN mkdir -p /home/sysadmin/.ssh && \ + chown sysadmin:sysadmin /home/sysadmin/.ssh && \ + chmod 700 /home/sysadmin/.ssh + +# Create privilege separation directory for SSH +RUN mkdir -p /run/sshd + +# Add Apache2's www-data user to sudo group and enable passwordless startup +RUN adduser www-data sudo +COPY web/config-web/www-data_startupservice /etc/sudoers.d/www-data_startupservice + +# Add Apache2 service startup script +COPY web/config-web/startupservice.sh /startupservice.sh +RUN chmod +x /startupservice.sh +CMD ["sudo", "--preserve-env", "/startupservice.sh"] + +# Expose SSH port +EXPOSE 22 + +# Expose ports for Apache +EXPOSE 80 + +# Enable Apache modules +RUN a2enmod headers +RUN a2enmod php8.2 +RUN a2enmod rewrite + +# Configure PHP +COPY web/config-web/90-local.ini /etc/php/8.2/apache2/conf.d/ + +# Install WordPress CLI (WP-CLI) +# https://wp-cli.org/#installing +RUN curl -L \ + https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ + -o wp-cli.phar \ + && chmod +x wp-cli.phar \ + && mv wp-cli.phar /usr/local/bin/wp + +# Create WP-CLI directory for www-data +RUN mkdir /var/www/.wp-cli +RUN chown -R www-data:www-data /var/www/.wp-cli + +# Create the dev directory and set permissions +RUN mkdir -p /var/www/dev/wp-content/uploads +RUN chown -R www-data:www-data /var/www/dev + +# Use WP-CLI to install WordPress +USER www-data +WORKDIR /var/www/dev +ARG WP_VERSION +RUN wp core download --version=$WP_VERSION + +# Add WordPress basic configuration +# 1) Download wp-config-docker.php for use as wp-config.php. Friendly view at: +# https://github.com/docker-library/wordpress/blob/master/latest/php8.2/apache/wp-config-docker.php +RUN curl -L \ + https://raw.githubusercontent.com/docker-library/wordpress/master/latest/php8.2/apache/wp-config-docker.php \ + -o /var/www/dev/wp-config.php + +# 2) Use awk to replace all instances of "put your unique phrase here" with a +# properly unique string (for AUTH_KEY and friends to have safe defaults if +# they aren't specified with environment variables) +# Based on: +# https://github.com/docker-library/wordpress/blob/master/latest/php8.2/apache/docker-entrypoint.sh +RUN awk ' \ + /put your unique phrase here/ { \ + cmd = "head -c1m /dev/urandom | sha1sum | cut -d\\ -f1"; \ + cmd | getline str; \ + close(cmd); \ + gsub("put your unique phrase here", str); \ + } \ + { print } \ + ' /var/www/dev/wp-config.php > /var/www/dev/wp-config.tmp \ + && mv /var/www/dev/wp-config.tmp /var/www/dev/wp-config.php + diff --git a/web/config-web/90-local.ini b/web/config-web/90-local.ini new file mode 100644 index 0000000..4dff7a9 --- /dev/null +++ b/web/config-web/90-local.ini @@ -0,0 +1,15 @@ +; configuration for local php customizations +; priority=90 + +; This directive determines which super global arrays are registered when PHP +; starts up. G,P,C,E & S are abbreviations for the following respective super +; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty +; paid for the registration of these arrays and because ENV is not as commonly +; used as the others, ENV is not recommended on productions servers. You +; can still get access to the environment variables through getenv() should you +; need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; https://php.net/variables-order +variables_order = "EGPCS" diff --git a/web/config-web/etc-apache2-sites-available/000-default.conf b/web/config-web/etc-apache2-sites-available/000-default.conf new file mode 100644 index 0000000..4701b2d --- /dev/null +++ b/web/config-web/etc-apache2-sites-available/000-default.conf @@ -0,0 +1,154 @@ +ServerName localhost:8080 + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + RewriteEngine On + + ########################################################################### + # Ensure plaintext files are served using UTF-8 + AddCharset utf-8 .txt + # Set conservative/secure defaults + + AllowOverride None + DirectoryIndex index.html + Options -Indexes + + # Git + + Require all denied + + # Subversion + + Require all denied + + # Deny access to accidental uploads of macOS-specific directories and files + # .DS_Store + + Require all denied + + # resource forks + + Require all denied + + + ########################################################################### + # CC Legal Tools + # Directory Aliases + Alias /status /var/www/git/cc-legal-tools-data/docs/status + Alias /rdf /var/www/git/cc-legal-tools-data/docs/rdf + Alias /publicdomain /var/www/git/cc-legal-tools-data/docs/publicdomain + Alias /licenses /var/www/git/cc-legal-tools-data/docs/licenses + Alias /cc-legal-tools /var/www/git/cc-legal-tools-data/docs/cc-legal-tools + # File Aliases + Alias /schema.rdf /var/www/git/cc-legal-tools-data/docs/rdf/schema.rdf + Alias /ns.html /var/www/git/cc-legal-tools-data/docs/rdf/ns.html + Alias /ns /var/www/git/cc-legal-tools-data/docs/rdf/ns.html + + # Disable .htaccess (for security and performance) + AllowOverride None + # Also serve HTML files without .html extension + RewriteCond %{REQUEST_FILENAME}.html -f + RewriteRule !.*\.html$ %{REQUEST_FILENAME}.html [L] + # Redirect .../index.php to .../ + RewriteCond %{REQUEST_FILENAME} "index\.php$" [NC] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule (.*/)index\.php$ $1 [L,NC,R=301] + # Deny access to PHP files (content should be only static files) + RewriteRule .*\.php$ "-" [F,L] + # Correct mimetype for .../rdf files + RewriteRule (.*/rdf$) $1 [T=application/rdf+xml] + # Enable CORS (cross-origin resource sharing) + Header set Access-Control-Allow-Origin "*" + + Include /var/www/git/cc-legal-tools-data/config/language-redirects + RedirectPermanent /licenses/work-html-popup /choose + RedirectPermanent /licenses/publicdomain/ /publicdomain/ + RedirectPermanent /licenses/mark/1.0 /publicdomain/mark/1.0 + RedirectPermanent /licences /licenses + + ########################################################################### + # Chooser +# Alias /choose /var/www/git/chooser/docs +# +# # Disable .htaccess (for security and performance) +# AllowOverride None +# # Redirect .../index.php to .../ +# RewriteCond %{REQUEST_FILENAME} "index\.php$" [NC] +# RewriteCond %{REQUEST_FILENAME} !-f +# RewriteRule (.*/)index\.php$ $1 [L,NC,R=301] +# # Deny access to PHP files (content should be only static files) +# RewriteRule .*\.php "-" [F,L] +# + RedirectPermanent /choose/zero /choose + RedirectPermanent /chooser /choose + RedirectTemp /choose https://chooser-beta.creativecommons.org + + ########################################################################### + # FAQ + Alias /faq /var/www/git/faq/faq + + # Disable .htaccess (for security and performance) + AllowOverride None + # Redirect .../index.php to .../ + RewriteCond %{REQUEST_FILENAME} "index\.php$" [NC] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule (.*/)index\.php$ $1 [L,NC,R=301] + # Deny access to PHP files (content should be only static files) + RewriteRule .*\.php "-" [F,L] + + + ########################################################################### + # Platform Toolkit + Alias /platform/toolkit /var/www/git/mp/docs + + # Disable .htaccess (for security and performance) + AllowOverride None + # Redirect .../index.php to .../ + RewriteCond %{REQUEST_FILENAME} "index\.php$" [NC] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule (.*/)index\.php$ $1 [L,NC,R=301] + # Deny access to PHP files (content should be only static files) + RewriteRule .*\.php "-" [F,L] + + + ########################################################################### + # WordPress/Default + DocumentRoot /var/www/dev + + # Expected configuration for WordPress + # (see conf-available/docker-php.conf) + AllowOverride All + DirectoryIndex index.php index.html + + # WP-API + RewriteRule ^(/wp-json/.*)$ /index.php$1 [L] + + # Legacy theme (hotlinked image that gives CC exposure) + RedirectPermanent /wp-content/themes/cc/images/cc.logo.white.svg https://ccstatic.org/cc2016www/images/cc.logo.white.svg + + # Permalinks (for dirs/files not found) + # https://codex.wordpress.org/Using_Permalinks + # Directory Conditions + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !^/rdf + RewriteCond %{REQUEST_URI} !^/publicdomain + RewriteCond %{REQUEST_URI} !^/platform/toolkit + RewriteCond %{REQUEST_URI} !^/licen[cs]es + RewriteCond %{REQUEST_URI} !^/faq + RewriteCond %{REQUEST_URI} !^/choose + RewriteCond %{REQUEST_URI} !^/cc-legal-tools + # File Conditions + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} !^/schema.rdf$ + RewriteCond %{REQUEST_URI} !^/ns.html$ + RewriteCond %{REQUEST_URI} !^/ns$ + # Rule + RewriteRule . /index.php [L] + + + + + +# vim: ft=apache ts=4 sw=4 sts=4 sr et diff --git a/web/config-web/startupservice.sh b/web/config-web/startupservice.sh new file mode 100644 index 0000000..324dfa2 --- /dev/null +++ b/web/config-web/startupservice.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -o errexit +set -o nounset + +# https://en.wikipedia.org/wiki/ANSI_escape_code +E0="$(printf "\e[0m")" # reset +E1="$(printf "\e[1m")" # bold + +/sbin/apache2ctl -v +echo "${E1}Starting webserver: http://127.0.0.1:8080${E0}" +# Start Apache in the foreground +/sbin/apache2ctl -D FOREGROUND -k start + +# Start SSH service +#/usr/sbin/sshd diff --git a/web/config-web/www-data_startupservice b/web/config-web/www-data_startupservice new file mode 100644 index 0000000..fc8ccf5 --- /dev/null +++ b/web/config-web/www-data_startupservice @@ -0,0 +1,5 @@ +# vim: ft=sudoers +# +# This file MUST be edited with `/usr/sbin/visudo -sf FILENAME`. + +%sudo ALL = NOPASSWD:SETENV: /startupservice.sh