Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Neil Dunbar committed Nov 14, 2013
0 parents commit 18b026e
Show file tree
Hide file tree
Showing 8 changed files with 1,448 additions and 0 deletions.
46 changes: 46 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from ubuntu:12.04

# Configure apt
run echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >> /etc/apt/sources.list
run apt-get -y update
run apt-get -y install python-software-properties
run apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
run add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu precise main'
run apt-key adv --recv-keys --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A

# Make apt and MariaDB happy with the docker environment
run echo "#!/bin/sh\nexit 101" >/usr/sbin/policy-rc.d
run chmod +x /usr/sbin/policy-rc.d
run cat /proc/mounts >/etc/mtab

# Install MariaDB
run apt-get -y update
run apt-get -y install
run LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y iproute mariadb-galera-server galera rsync netcat-openbsd socat pv

# this is for testing - can be commented out later
run LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y iputils-ping net-tools

run add-apt-repository 'deb http://repo.percona.com/apt precise main'
run apt-get -y update
run LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y percona-xtrabackup

# add in extra wsrep scripts
add wsrep_sst_common /usr/bin/wsrep_sst_common
add wsrep_sst_xtrabackup-v2 /usr/bin/wsrep_sst_xtrabackup-v2

# Clean up
run rm /usr/sbin/policy-rc.d
run rm -r /var/lib/mysql

# Add config(s) - standalong and cluster mode
add ./my-cluster.cnf /etc/mysql/my-cluster.cnf
add ./my-init.cnf /etc/mysql/my-init.cnf

expose 3306 4567

add ./mariadb-setrootpassword /usr/bin/mariadb-setrootpassword
add ./mariadb-start /usr/bin/mariadb-start
cmd ["/usr/bin/mariadb-start"]

# vim:ts=8:noet:
175 changes: 175 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
MariaDB 5.5 Galera
==================

A reasonably simple Ubuntu 12.04 LTS container with the pieces to form
a MariaDB 5.5 Galera cluster. It's based on Nick Stenning's MariaDB
5.5 container for MariaDB 5.5, and adds in default support for X.509
based administrative authentication.

(Repeating the warning from Nick S)

**NB**: Please be aware that by default docker will make the MariaDB
port accessible from anywhere if the host firewall is unconfigured. It
also exposes the Galera wsrep port (by default 4567) globally.

The root password for the first node (and therefore the entire
cluster) is randomly generated. This password can only be used to
contact the server along the unix domain socket (ie, the default
localhost connection).

The container needs to import two volumes (one read/write, the other
read only). The first volume is the data volume, where the actual
MySQL data is stored.

The second volume (read-only) is the SSL container, which should
contain 3 files:

* a CA certificate (this can be a real CA certificate, or just a
simple test one, generated via something like TinyCA. This CA
file should be called `ca.pem`. [^1]
* an SSL private key. This should be set to ownership mode 0600 (or
even 0400). The file needs to be called `server-key.pem`.
* an SSL server certificate. The CN component of the subject name on
this certificate should be the DNS name of the host which will be
the end point for the database service. Note that you can have
multiple host names by using the subjectAltName X.509 v3 extension
on the certificate. This file needs to be called `server-cert.pem`.

The data volume must be mounted at the container directory
`/var/lib/mysql`.

The SSL volume must be mounted at the container directory
`/etc/ssl/mysql`.

Within the SSL volume there should also be a directory called `root`
which will contain a set of *client* certificates with names like
`joe-root.pem`, `amy-root.pem`, `superman.pem` and so on. These names
will be registered as root capable users from any network host, with
no passwords, but only available using the SSL client certificates.
[^2]

The remote root access is derived from a set of X.509 certificates
which will be used via SSL to authenticate the user, so that passwords
are not passed in via environmental variables. The root password is
also encrypted via those keys and stored on the (exported) data volume
which the container needs.

Decrypting the Root Password
----------------------------

If you need to recover the root password, and you are the holder of
one of the root keys in /etc/ssl/mysql/root, then execute

$ cat _/path/to/mysql-dir/_rootpw.pem | openssl smime -decrypt -inkey
_path/to/private-key.pem_

e.g.

cat /data/mysql-node-1/rootpw.pem | openssl smime -decrypt -inkey
~/ssl-keys/joe-root-key.pem


Example Usage
-------------

This sets up a 3 node cluster on 3 different servers, all using
default ports.

We assume that /data/mysql holds the (host, not container) directory
where the MySQL database files need to go, and /data/mysql-ssl
contains the relevant keys, certificates and CA certificates. For the
first node standup, /data/mysql should be empty.

# Boot the first node in standalone mode #

DBID=$(sudo docker run -d -v /data/mysql:/var/lib/mysql \
-v /data/mysql-ssl:/etc/ssl/mysql \
-p 3306:3306 -e CLUSTER=BOOT ndunbar/mariadb55 \
mariadb-start)

(Note: no need to expose ports 4567 and 4444 - no-one is talking to
anyone just yet)

This should fire up a single MySQL server with the appropriate remote
root user permissions (using SSL client certificates only). Check the
contents of `/data/mysql/mysql.error.log` for whether things worked OK
or not.

Assuming it went well, continue.

# Stop the standalone mode #

sudo docker stop $DBID

This should (gracefully) stop the standalone mode MariaDB node

# Start the node in Primary Component mode #

To begin the cluster, you need a single node as a catch-all donor.

DBID=$(sudo docker run -d /data/mysql:/var/lib/mysql \
-v /data/mysql-ssl:/etc/ssl/mysql \
-p 3306:3306 -p 4567:4567 -p 4444:4444 \
-e CLUSTER=INIT \
-e NODE_ADDR=my.host.name ndunbar/mariadb55 \
mariadb-start)

This should restart the server (with data intact) with the ability to
use it to populate new nodes to fill out the cluster. Node that the
primary component has a node number of 1 (this is fixed by scripting).

Note that ports 4567 and 4444 are open. 4567 is the Galera clustering
service, and 4444 is the service by which snapshots of the database
are transferred across to bring a new node into sync. The current
container implementation uses Xtrabackup, encrypting the transfers via
SSL between nodes. Xtrabackup has the nice property of not blocking
the donor server (well, it does, but only for a very short time),
which means you can continue to write into the donor node while other
nodes are coming online.

# Start the other nodes in the cluster

For each extra node that you need to turn up, set up the `/data/mysql`
and `/data/mysql-ssl` directories as per the primary node, and execute

DBID=$(sudo docker run -d /data/mysql:/var/lib/mysql \
-v /data/mysql-ssl:/etc/ssl/mysql \
-p 3306:3306 -p 4567:4567 -p 4444:4444 \
-e CLUSTER= my.host.name,his.host.name,her.host.name \
-e NODE=<node number> \
-e NODE_ADDR=my.host.name ndunbar/mariadb55 \
mariadb-start)

where `<node number>` is some integer above 1, ideally sequential. The
`his.host.name`, `her.host.name` etc. should be the other nodes in the
cluster. You don't need to list all the nodes in the cluster - the
service will discover the other active nodes upon attempting to join
the cluster - if the list includes the host name of the node on which
the server is running, it will be excluded from the list of nodes to
be interrogated for cluster manifest.

Three nodes is the least number needed to avoid split brain
scenarios.

# (Optional) Restart the primary node as a normal cluster node #

Once the minimum number of synced nodes the cluster reaches 3, you can
stop and restart the primary component as just another node in the
cluster.

(On the server running the primary node)

sudo docker stop $DBID

DBID=$(sudo docker run -d /data/mysql:/var/lib/mysql \
-v /data/mysql-ssl:/etc/ssl/mysql \
-p 3306:3306 -p 4567:4567 -p 4444:4444 \
-e CLUSTER= my.host.name,his.host.name,her.host.name \
-e NODE=1 \
-e NODE_ADDR=my.host.name ndunbar/mariadb55 \
mariadb-start)

[^1]: http://tinyca.sm-zone.net

[^2]: Note that the root user certificates _must_ be signed by the
same CA as the one signing the server certificate.
114 changes: 114 additions & 0 deletions mariadb-setrootpassword
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/bin/bash

set -u

MSA=/usr/bin/mysqladmin
# generate long random password
MARIADB_ROOT_PW=$(openssl rand -base64 32)


# make sure ownership of data dir is OK
chown -R mysql:mysql /var/lib/mysql
/usr/bin/mysqld_safe &



sleep 5 # wait for mysqld_safe to rev up, and check for port 3306
port_open=0

while [ "$port_open" -eq 0 ]; do
/bin/nc -z -w 5 127.0.0.1 3306
if [ $? -ne 0 ]; then
echo "Sleeping waiting for port 3306 to open: result " $?
sleep 1
else
echo "Port 3306 is open"
port_open=1
fi
done

# Secure the installation
done=0
count=0
maxtries=10
while [ $done -eq 0 ]; do
${MSA} -u root password ${MARIADB_ROOT_PW}
if [ $? -ne 0 ]; then
count=$((${count} + 1))
if [ $count -gt $maxtries ]; then
echo "Maximum tries at setting password exceeded. Giving up"
exit 1
else
echo "Root password set failed. Sleeping, then retrying"
sleep 1
fi
else
echo "Root Password set successfully"
done=1
fi
done

# this code mimics the secure install script, which was originally
# scripted via expect. I found that unreliable, hence this section

# drop test database
echo "Dropping test DB"
DROP="DROP DATABASE IF EXISTS test"
echo "$DROP" | mysql -u root --password="$MARIADB_ROOT_PW" mysql

echo "Cleaning test db privs"
# remove db privs for test
DELETE="DELETE FROM mysql.db Where Db='test' OR Db='test\\_%'"
echo "$DELETE" | mysql -u root --password="$MARIADB_ROOT_PW" mysql

echo "Deleting anon db users"
# remove anon users
DELETE="DELETE FROM mysql.user WHERE User=''"
echo "$DELETE" | mysql -u root --password="$MARIADB_ROOT_PW" mysql

echo "create mysql user"
# create mysql@localhost user for xtrabackup
CUSER="CREATE USER 'mysql'@'localhost'"
echo "$CUSER" | mysql -u root --password="$MARIADB_ROOT_PW" mysql

# now set the root passwords given the certificates available
ROOTCERTS=/etc/ssl/mysql/root
if [ -d ${ROOTCERTS} ]; then
clist=$(find ${ROOTCERTS} -type f -a -name \*.pem -print)

if [ -z "${clist}" ]; then
echo "No certificates available to encrypt root pw" >&2
exit 1
fi

# dumping encrypted root password to disk - make sure that
# only owner can read/write it
oldu=$(umask)
umask 0077
echo -n $MARIADB_ROOT_PW | openssl smime -encrypt -aes256 \
-out /var/lib/mysql/rootpw.pem \
${clist}
umask ${oldu}

for c in ${clist}; do
filename=$(basename "${c}")
username=${filename%.*}
# extract subject and issuer
subject=$(openssl x509 -noout -subject -in ${c} | sed -e "s/^subject= //ig")
printf -v qsubject "%q" "${subject}"
issuer=$(openssl x509 -noout -issuer -in ${c} | sed -e "s/^issuer= //ig" -e "")
printf -v qissuer "%q" "${issuer}"
CUSER="CREATE USER '${username}'@'%'"
echo "${CUSER}" | mysql -u root --password="$MARIADB_ROOT_PW" mysql
GRANT="GRANT ALL PRIVILEGES ON *.* TO '${username}'@'%' \
REQUIRE SUBJECT '${qsubject}' AND \
ISSUER '${qissuer}' \
WITH GRANT OPTION"
echo "${GRANT}" | mysql -u root --password="$MARIADB_ROOT_PW" mysql
done
fi
echo "FLUSH PRIVILEGES" | mysql -u root --password="$MARIADB_ROOT_PW" mysql

echo "Shutting down MySQL server"
${MSA} -uroot -p${MARIADB_ROOT_PW} shutdown
echo "---> MariaDB installation secured with root password" >&2
Loading

0 comments on commit 18b026e

Please sign in to comment.