Skip to content
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

Add support for local certificates #251

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
local.yml
assets/certs/certs
assets/certs/ca
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,41 @@ When you change them, use `bin/moodle-docker-compose down && bin/moodle-docker-c
| `MOODLE_DOCKER_APP_RUNTIME` | no | 'ionic3' or 'ionic5' | not set | Set this to indicate the runtime being used in the Moodle app. In most cases, this can be ignored because the runtime is guessed automatically (except on Windows using the `.cmd` binary). In case you need to set it manually and you're not sure which one it is, versions 3.9.5 and later should be using Ionic 5. |
| `MOODLE_DOCKER_APP_NODE_VERSION` | no | [node](https://hub.docker.com/_/node) image version tag | not set | Node version to run the app. In most cases, this can be ignored because the version is parsed from the project's `.nvmrc` file. This will only be used when the runtime is `ionic5` and the app is running from the local filesystem. |

## SSL certificates

If you wish, you can generate a self-signed certificate repository and create your own certificates for your containers.

Certificates should be placed into the `assets/certs/certs` directory, with the certificate authority placed into `assets/certs/ca`.

If moodle-docker-compose detects the certificate for a local certificate authority at `asserts/certs/ca/ca.pem` then an additional SSL configuration will be included.

To make this easier, a helper has been created for Linux and MacOS which will:

- generate a new certificate authority if required
- generate certificates
- offer to install your new Certificate Authority


The helper can be used as follows:

```
./assets/certs/createcerts.sh hostname [alternative-hostname]
```

You will need to run a separate command for each host you wish to generate a certificate for, for example:

```
./asserts/certs/createcerts.sh webserver webserver.container.docker.internal
./asserts/certs/createcerts.sh exttests exttests.container.docker.internal
```

### Local hostnames

If you wish to access your containers using their certificates, you will need to add either:

- entries to your `/etc/hosts` or `%WinDir%\System32\Drivers\Etc\Hosts` file; or
- relevant DNS entries to your local DNS resolver (for example dnsmasqd).

## Local customisations

In some situations you may wish to add local customisations, such as including additional containers, or changing existing containers.
Expand Down
9 changes: 9 additions & 0 deletions assets/certs/443-default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<VirtualHost *:443>
ServerName webserver
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
SSLEngine on
SSLCertificateFile "/etc/ssl/certs/moodle/webserver.crt"
SSLCertificateKeyFile "/etc/ssl/certs/moodle/webserver.key"
</VirtualHost>
19 changes: 19 additions & 0 deletions assets/certs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# IMPORTANT

These are test Certificates and Keys only!

Do NOT use them outside of a closed development environment.

DO NOT commit them to the git repository.

## Generating new certificates

A helper script exists at the project root and takes a list of names for the certificate.

For example:

```
./createcerts.sh webserver webserver.container.docker.internal
```

Each argument is used as a subject alternative name for the certificate.
155 changes: 155 additions & 0 deletions assets/certs/createcerts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/bin/bash

CERTDIR="`pwd`/assets/certs/"

CACONF="${CERTDIR}/openssl.cnf"
CAKEY="${CERTDIR}/ca/ca.key"
CACERT="${CERTDIR}/ca/ca.pem"

if [ -f "$CAKEY" ] && [ -f "${CACERT}" ]; then
echo "Using existing CA private key"
echo
else
# Generate the private key for the CA:
echo "Generating the key and certificate for the CA server"
mkdir -p "${CERTDIR}/ca"
mkdir -p "${CERTDIR}/certs"

# Generate the key and certificate for the CA.
cat <<EOF | openssl req -config ${CACONF} -nodes -new -x509 -keyout "${CAKEY}" -out "${CACERT}"
AU
Western Australia
Perth
Moodle Pty Ltd
Moodle LMS


EOF

echo "Generated an OpenSSL Certificate Authority"
touch "${CERTDIR}/ca/index.txt"
echo '01' > "${CERTDIR}/ca/serial.txt"
echo
echo "You should add this certificate to your root certificate store."

OS=`uname -s`
if [ "${OS}" = "Darwin" ]
then
echo "You can use the following command:"
echo "sudo security add-trusted-cert -d -r trustRoot -k '/Library/Keychains/System.keychain' ${CACERT}"
read -p "Do you want me to do that for you now? " yn
case $yn in
[Yy]* ) sudo security add-trusted-cert -d -r trustRoot -k '/Library/Keychains/System.keychain' "${CACERT}"; break;;
esac
fi

if [ "${OS}" = "Linux" ]
then
echo "You can use the following command:"
echo "sudo cp ${CERTDIR}/ca/ca.pem usr/local/share/ca-certificates/moodle-docker-ca.crt && sudo update-ca-certificates"
read -p "Do you want me to do that for you now? " yn
case $yn in
[Yy]* ) sudo cp "${CERTDIR}/ca/ca.pem" usr/local/share/ca-certificates/moodle-docker-ca.crt && sudo update-ca-certificates; break;;
esac

fi
fi

if [ "$#" -lt 1 ]
then
echo "Usage: Must supply at least one hostname."
exit 1
fi

# The first hostname is canonical.
DOMAIN=$1

HOSTKEY="${CERTDIR}/certs/${DOMAIN}.key"
HOSTCSR="${CERTDIR}/certs/${DOMAIN}.csr"
HOSTCRT="${CERTDIR}/certs/${DOMAIN}.crt"
HOSTEXT="${CERTDIR}/certs/${DOMAIN}.ext"

# Create a private key for the dev site:
echo
echo "Generating a private key for the $DOMAIN dev site"
echo
openssl genrsa -out "${HOSTKEY}" 2048

echo "Generating a CSR for $DOMAIN"
cat <<EOF | openssl req -nodes -new -key "${HOSTKEY}" -out "${HOSTCSR}"
AU
Western Australia
Perth
Moodle Pty Ltd
Moodle LMS


EOF
echo

DNSCOUNT=1
for var in "$@"
do
DNS=$(cat <<-EOF
${DNS}
DNS.${DNSCOUNT} = ${var}
EOF
)
DNSCOUNT=$((DNSCOUNT + 1))
done

cat > "${HOSTEXT}" << EOF
[ req ]
default_bits = 2048
default_keyfile = ${HOSTKEY}
distinguished_name = server_distinguished_name
req_extensions = server_req_extensions
string_mask = utf8only

[ server_distinguished_name ]

countryName = Country Name (2 letter code)
countryName_default = AU

stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Western Australia

localityName = Locality Name (eg, city)
localityName_default = Perth

organizationName = Organization Name (eg, company)
organizationName_default = Moodle Pty Ltd

organizationalUnitName = Organizational Unit (eg, division)
organizationalUnitName_default = Moodle LMS

commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_default = ${DOMAIN}

emailAddress = Email Address
emailAddress_default = [email protected]

[ server_req_extensions ]
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alternate_names
[ alternate_names ]
$DNS
EOF

#Next run the command to create the certificate: using our CSR, the CA private key, the CA certificate, and the config file:
echo "Generating a certificate for $DOMAIN"
cat <<EOF | openssl req -config "${HOSTEXT}" -newkey rsa:2048 -sha256 -nodes -out "${HOSTCSR}" -outform PEM
AU
Western Australia
Perth
Moodle Pty Ltd
Moodle LMS


EOF
echo

echo "Signing the request"
openssl ca -config "${CACONF}" -policy signing_policy -extensions signing_req -out "${HOSTCRT}" -infiles "${HOSTCSR}"
4 changes: 4 additions & 0 deletions assets/certs/enable_ssl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -Eeo pipefail

a2enmod ssl
4 changes: 4 additions & 0 deletions assets/certs/install_ca.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -Eeo pipefail

update-ca-certificates
85 changes: 85 additions & 0 deletions assets/certs/openssl.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
HOME = .
RANDFILE = $ENV::HOME/.rnd

####################################################################
[ ca ]
default_ca = CA_default # The default ca section

[ CA_default ]

default_days = 365 # How long to certify for
default_crl_days = 30 # How long before next CRL
default_md = sha256 # Use public key default MD
preserve = no # Keep passed DN ordering

x509_extensions = ca_extensions # The extensions to add to the cert

email_in_dn = no # Don't concat the email in the DN
copy_extensions = copy # Required to copy SANs from CSR to cert

base_dir = assets/certs
certificate = $base_dir/ca/ca.pem # The CA certifcate
private_key = $base_dir/ca/ca.key # The CA private key
new_certs_dir = $base_dir/certs # Location for new certs after signing
database = $base_dir/ca/index.txt # Database index file
serial = $base_dir/ca/serial.txt # The current serial number

unique_subject = no # Set to 'no' to allow creation of
# several certificates with same subject.


####################################################################
[ req ]
default_bits = 4096
default_keyfile = cakey.pem
distinguished_name = ca_distinguished_name
x509_extensions = ca_extensions
string_mask = utf8only

####################################################################
[ ca_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = AU

stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Western Australia

localityName = Locality Name (eg, city)
localityName_default = Perth

organizationName = Organization Name (eg, company)
organizationName_default = Moodle Pty Ltd

organizationalUnitName = Organizational Unit (eg, division)
organizationalUnitName_default = Moodle LMS

commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_default = Testing CA

emailAddress = Email Address
emailAddress_default = [email protected]

####################################################################
[ ca_extensions ]

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical, CA:true
keyUsage = keyCertSign, cRLSign

####################################################################
[ signing_policy ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

####################################################################
[ signing_req ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
26 changes: 26 additions & 0 deletions assets/docker-php-entrypoint-moodle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -Eeo pipefail

docker_process_init_files() {
echo
local f
for f; do
case "$f" in
*.sh)
if [ -x "$f" ]; then
echo "$0: running $f"
"$f"
else
echo "$0: sourcing $f"
. "$f"
fi
;;
esac
done
}

echo "Running entrypoint files from /docker-entrypoint-initdb.d/*"
docker_process_init_files /docker-entrypoint-initdb.d/*

echo "Starting docker=php-entrypoint with $@"
/usr/local/bin/docker-php-entrypoint "$@"
6 changes: 6 additions & 0 deletions base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ services:
volumes:
- "${MOODLE_DOCKER_WWWROOT}:/var/www/html"
- "${ASSETDIR}/web/apache2_faildumps.conf:/etc/apache2/conf-enabled/apache2_faildumps.conf"
- "${ASSETDIR}/docker-php-entrypoint-moodle:/usr/local/bin/docker-php-entrypoint-moodle"
environment:
MOODLE_DOCKER_DBNAME: moodle
MOODLE_DOCKER_DBUSER: moodle
MOODLE_DOCKER_DBPASS: "m@0dl3ing"
MOODLE_DOCKER_BROWSER: firefox
MOODLE_DOCKER_WEB_HOST: "${MOODLE_DOCKER_WEB_HOST}"
entrypoint: 'docker-php-entrypoint-moodle'
command: 'apache2-foreground'
exttests:
image: moodlehq/moodle-exttests
volumes:
- "${ASSETDIR}/exttests/apache2_ports.conf:/etc/apache2/ports.conf"
- "${ASSETDIR}/exttests/apache2.conf:/etc/apache2/sites-enabled/000-default.conf"
- "${ASSETDIR}/docker-php-entrypoint-moodle:/usr/local/bin/docker-php-entrypoint-moodle"
entrypoint: 'docker-php-entrypoint-moodle'
command: 'apache2-foreground'
selenium:
image: "selenium/standalone-firefox${MOODLE_DOCKER_SELENIUM_SUFFIX:-}:${MOODLE_DOCKER_BROWSER_TAG}"
volumes:
Expand Down
5 changes: 5 additions & 0 deletions bin/moodle-docker-compose
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ fi
dockercompose="${dockercompose} -f ${basedir}/base.yml"
dockercompose="${dockercompose} -f ${basedir}/service.mail.yml"

if [ -f assets/certs/ca/ca.pem ]
then
dockercompose="${dockercompose} -f ${basedir}/ssl.yml"
fi

# PHP Version.
export MOODLE_DOCKER_PHP_VERSION=${MOODLE_DOCKER_PHP_VERSION:-8.0}

Expand Down
Loading