Commit f90242ee authored by Robert Schambach's avatar Robert Schambach
Browse files

Add secure database setup

parent bdf152f3
MariaDB Session Templates
===
This repository contains the following SCONE session templates for MariaDB:
* MariaDB server (`db_session.yml`): deploy a MariaDB server with full data-at-rest encryption and filesystem authentication, including dynamic libraries. Bootstrap of the database (e.g. creation of the root user and mysql database) is also attested, allowing the bootstrap to be performed in untrusted environments.
* MariaDB simple client (`db_simpleclient.yml`): a simple SCONE application to test your MariaDB setup. Creates a database and a few records. Used as a test in [our MariaDB helm chart](https://sconedocs.github.io/helm_mariadb/).
Submit policies
---
#### Build an encrypted MariaDB image
The session template for the server assumes that the bootstrap will be attested. That said, you need to build a MariaDB image with an encrypted filesystem. Use the scripts in `utils/`. The steps below assume that your current directory is `scone-templates/mariadb`.
```bash
# Define your base MariaDB image.
export BASE_IMAGE=sconecuratedimages/apps:mariadb-10.4-alpine-scone5.0.0
# Define the image we are building. This one will be deployed to your servers.
export TARGET_IMAGE=myprivaterepo/apps:mariadb-10.4-alpine-prod
# Download the latests images from sconecuratedimages.
./pull_latest_images.sh
# Build.
key_tag=$(docker build --no-cache --build-arg BASE_IMAGE=${BASE_IMAGE} -t ${TARGET_IMAGE} utils/ | grep "Encrypted file system protection")
```
Extract the key and tag and create a script that will be consumed by `upload_policies.sh`.
```bash
SCONE_FSPF_KEY=$(echo $key_tag | awk '{print $11}')
SCONE_FSPF_TAG=$(echo $key_tag | awk '{print $9}')
echo "export DB_POLICY_FSPF_KEY=$SCONE_FSPF_KEY" > fspf_variables.sh
echo "export DB_POLICY_FSPF_TAG=$SCONE_FSPF_TAG" >> fspf_variables.sh
```
Finally, push your image to a registry.
```bash
docker push $TARGET_IMAGE
```
#### Upload policies
The default values for the templates are already defined in `upload_policies.sh`. Open and check it. You can change it to better suit your needs.
If you are deploying MariaDB with Helm, you need to define the release name in advance, so the uploaded sessions will have the correct Kubernetes service names.
```bash
export RELEASE_NAME=mariadb
```
Once you are finished, it is time to submit the policies.
Export your CAS address. This example uses the public CAS hosted at 4-2-1.scone-cas.cf.
```bash
export SCONE_CAS_ADDR=5-0-0.scone-cas.cf
```
Submit your policies with the help of SCONE CLI:
```bash
docker run -it --rm -e SCONE_CAS_ADDR=$SCONE_CAS_ADDR -e SCONE_LAS_ADDR=172.17.0.1 -e RELEASE_NAME=$RELEASE_NAME --device /dev/isgx -v $PWD:/policies sconecuratedimages/sconecli:alpine3.7-scone5.0.0 bash /policies/upload_policies.sh
```
#### Run MariaDB
Now it is time to deploy your server, and there should be a `myenv` in your current directory. This file is generated by the previous step and contains all the needed information to retrieve the configurations that you've just submitted.
Export its values to the environment:
```bash
source myenv
```
Now you can install the MariaDB Helm chart:
```bash
helm install $RELEASE_NAME sconeapps/mariadb-scone \
--set image=$TARGET_IMAGE \
--set scone.attestation.cas=$SCONE_CAS_ADDR \
--set scone.attestation.DBConfigID=$DB_CONFIG_ID/db \
--set scone.attestation.bootstrapConfigID=$DB_CONFIG_ID/bootstrap \
--set scone.attestation.createUserConfigID=$DB_CONFIG_ID/create_user \
--set scone.attestation.testCreateDBConfigID=$SIMPLE_CLIENT_CONFIG_ID/create_db \
--set scone.attestation.testQueriesConfigID=$SIMPLE_CLIENT_CONFIG_ID/queries
```
Check [our MariaDB helm chart docs](https://sconedocs.github.io/helm_mariadb/) for more information on how to deploy and customize MariaDB.
name: $DB_SESSION
version: "0.3"
# Access control:
# - only the data owner (CREATOR) can read or update the session
# - even the data owner cannot read the session secrets (i.e., the volume key and tag)
access_policy:
read:
- CREATOR
update:
- CREATOR
security:
attestation:
tolerate: [debug-mode, hyperthreading, outdated-tcb]
ignore_advisories: "*"
# Service: mariadb
# https://mariadb.com/kb/en/securing-connections-for-client-and-server/
services:
- name: db
image_name: db_image
command: mysqld --innodb-use-native-aio=0 --innodb-flush-method=fsync
mrenclaves: ["$MRENCLAVE_MYSQLD"]
pwd: /
environment:
MYSQL_ROOT_PASSWORD: "$$SCONE::MYSQL_ROOT_PASSWORD$$"
MYSQL_ALLOW_EMPTY_PASSWORD: "" # Empty means 'false', anything else 'true'
MYSQL_RANDOM_ROOT_PASSWORD: "" # Empty means 'false', anything else 'true'
fspf_path: /fspf.pb
fspf_key: $DB_POLICY_FSPF_KEY
fspf_tag: $DB_POLICY_FSPF_TAG
- name: bootstrap
image_name: bootstrap_image
command: mysqld --bootstrap --basedir=/usr --datadir=/var/lib/mysql --log-warnings=0 --plugin-dir=/usr/lib/mariadb/plugin --innodb-use-native-aio=0 --user=mysql --max_allowed_packet=8M --net_buffer_length=16K --default-storage-engine=innodb
mrenclaves: ["$MRENCLAVE_MYSQLD", "$MRENCLAVE_MY_PRINT_DEFAULTS"]
pwd: /
environment:
MYSQL_ROOT_PASSWORD: "$$SCONE::MYSQL_ROOT_PASSWORD$$"
MYSQL_ALLOW_EMPTY_PASSWORD: "" # Empty means 'false', anything else 'true'
MYSQL_RANDOM_ROOT_PASSWORD: "" # Empty means 'false', anything else 'true'
fspf_path: /fspf.pb
fspf_key: $DB_POLICY_FSPF_KEY
fspf_tag: $DB_POLICY_FSPF_TAG
- name: create_user
image_name: bootstrap_image
command: ["mysql", "-e", "source /etc/create-user.sql;"]
mrenclaves: ["$MRENCLAVE_MYSQL"]
pwd: /
environment:
MYSQL_ROOT_PASSWORD: "$$SCONE::MYSQL_ROOT_PASSWORD$$"
MYSQL_ALLOW_EMPTY_PASSWORD: "" # Empty means 'false', anything else 'true'
MYSQL_RANDOM_ROOT_PASSWORD: "" # Empty means 'false', anything else 'true'
fspf_path: /fspf.pb
fspf_key: $DB_POLICY_FSPF_KEY
fspf_tag: $DB_POLICY_FSPF_TAG
# We inject the content of MariaDB cofiguration file including
# the certificate of the MariaDB as well as the CA certificate of the session
images:
- name: bootstrap_image
volumes:
- name: encrypted_datadir_volume
path: /var/lib/mysql
injection_files:
- path: /etc/create-user.sql
content: |
CREATE USER 'scontain'@'%'
REQUIRE SUBJECT '/CN=MARIADB_CLIENT_CERT'
AND ISSUER '/CN=MariaDB_CA';
GRANT ALL PRIVILEGES ON *.* TO 'scontain'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'scontain'@'%';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'scontain';
CREATE DATABASE 'document_db';
CREATE TABLE 'document_db.document'(
record_id INT PRIMARY KEY,
content VARCHAR(1000) NOT NULL
);
- name: db_image
volumes:
- name: encrypted_datadir_volume
path: /var/lib/mysql
injection_files:
- path: /etc/my.cnf
content: |
# This group is read both both by the client and the server
# use it for options that affect everything
[client-server]
# This group is read by the server
[mysqld]
user=mysql
skip-host-cache
skip-name-resolve
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# Network
bind-address = 0.0.0.0
port = 3306
# Encryption parameters
plugin_load_add = file_key_management
file_key_management_filename = /etc/keys.txt
file_key_management_encryption_algorithm = aes_cbc
encrypt_binlog = 1
innodb_encrypt_tables = ON
innodb_encrypt_log = ON
innodb_encryption_threads = 4
innodb_encryption_rotate_key_age = 0 # Do not rotate key
innodb-tablespaces-encryption = ON
encrypt_tmp_files = ON
innodb_log_group_home_dir = /external
innodb_data_home_dir = /external
# Enabling TLS for MariaDB server
ssl
ssl_cert = /etc/server.crt
ssl_key = /etc/server.key
ssl_ca = /etc/mariadb-ca.crt
# Enabling TLS for MariaDB clients
[client]
ssl_cert = /etc/client.crt
ssl_key = /etc/client.key
ssl-verify-server-cert
- path: /etc/keys.txt # key to encrypt MariaDB tables ; requires MariaDB in experimental with .hex extension
content: 1;D5C2C29814279240DD6BA4542F39B97B;107666757DC1E458232D881A183219E7F9042F6A96A78E748BF7EA90C98BB058
- path: /etc/mariadb-ca.crt
content: $$SCONE::MARIADB_CA_CERT.chain$$ # Export this session's CA certificate & chain
- path: /etc/server.crt
content: $$SCONE::mariadb.crt$$ # export MariaDB server certificate
- path: /etc/server.key
content: $$SCONE::mariadb.key$$
- path: /etc/client.crt
content: $$SCONE::MARIADB_CLIENT_CERT.crt$$ # export client certificate
- path: /etc/client.key
content: $$SCONE::MARIADB_CLIENT_CERT.key$$ # export client key
# Export client credentials from DB session.
# The client CA is the session CA of this session
secrets:
- name: MYSQL_ROOT_PASSWORD # automatically generate MYSQL_ROOT_PASSWORD cannot be seen by anyone at all
kind: ascii
value: "scontain"
- name: mariadb-key # automatically generate MariaDB server certificate
kind: private-key
- name: mariadb # automatically generate MariaDB server certificate
private_key: mariadb-key
issuer: MARIADB_CA_CERT
kind: x509
- name: MARIADB_CLIENT_KEY
kind: private-key
export:
- session: $DB_SIMPLECLIENT
- name: MARIADB_CLIENT_CERT # automatically generate client certificate
private_key: MARIADB_CLIENT_KEY
issuer: MARIADB_CA_CERT
common_name: MARIADB_CLIENT_CERT
kind: x509
export:
- session: $DB_SIMPLECLIENT # export client cert/key to upload session
- name: MARIADB_CA_KEY # export session CA certificate as MariaDB CA certificate
kind: private-key
- name: MARIADB_CA_CERT # export session CA certificate as MariaDB CA certificate
kind: x509-ca
common_name: MariaDB_CA
private_key: MARIADB_CA_KEY
export:
- session: $DB_SIMPLECLIENT # export the session CA certificate to upload session
volumes:
# No fspf key and tag: an encrypted volume will be automatically generated
# on the first run
- name: encrypted_datadir_volume
name: $DB_SIMPLECLIENT
version: "0.3"
# Access control:
# - only the data owner (CREATOR) can read or update the session
# - even the data owner cannot read the session secrets (i.e., the volume key and tag) or delete the session
access_policy:
read:
- CREATOR
update:
- CREATOR
security:
attestation:
tolerate: [debug-mode, hyperthreading, outdated-tcb]
ignore_advisories: "*"
services:
- name: serve
image_name: client_image
command: ["python3", "rest_api.py"]
mrenclaves: ["$MRENCLAVE_SIMPLECLIENT_FASTAPISERVER"]
pwd: /
images:
- name: client_image
injection_files:
- path: /etc/mariadb-ca.crt
content: $$SCONE::MARIADB_CA_CERT.chain$$ # Use the database session's CA certificate as a trusted root CA cert. We can use chain here because we verify the session name in the DB
- path: /etc/client.crt
content: $$SCONE::MARIADB_CLIENT_CERT.crt$$
- path: /etc/client.key
content: $$SCONE::MARIADB_CLIENT_CERT.key$$
# Import client credentials from DB session.
secrets:
- name: db_user
kind: ascii
value: $DB_USER
- name: MARIADB_CLIENT_CERT
import:
session: $DB_SESSION
secret: MARIADB_CLIENT_CERT
- name: MARIADB_CA_CERT
import:
session: $DB_SESSION
secret: MARIADB_CA_CERT
#!/bin/bash
set -e
# usage: get_mrenclave image [cmd docker_run_modifiers]
# return: mrenclave, if successful.
#
# e.g. get_mrenclave sconecuratedimages/apps:mariadb-10.4-alpine mysqld "-e SCONE_HEAP=2G"
#
# TODO: error handling.
function get_mrenclave {
r=$(docker run -it $3 --rm -e SCONE_HASH=1 $1 $2)
echo $r
}
# This script ensures that you have the latest version
# of all needed images, and creates a `mrenclaves.sh` file
# containing all up-to-date MRENCLAVES (can be easily exported
# into the environment).
CAS_IMAGE=${CAS_IMAGE:-"sconecuratedimages/services:cas.preprovisioned-scone5.0.0"}
MARIADB_IMAGE=${BASE_IMAGE:-"sconecuratedimages/apps:mariadb-10.4-alpine-scone5.0.0"}
FASTAPISERVER_IMAGE=${FASTAPISERVER_IMAGE:-"enterjazz/scone-test-images:fast-api-server"}
CLI_IMAGE=${CLI_IMAGE:-"sconecuratedimages/sconecli:alpine3.7-scone5.0.0"}
echo "Pulling the latest images. Make sure you have access to all of them!"
for img in $MARIADB_IMAGE $CAS_IMAGE $FASTAPISERVER_IMAGE $CLI_IMAGE; do
docker pull $img
done
echo "Determining the MRENCLAVES."
# Determine MRENCLAVE of latest images.
CAS_MRENCLAVE=$(get_mrenclave $CAS_IMAGE cas)
MRENCLAVE_MYSQLD=$(get_mrenclave $MARIADB_IMAGE mysqld "-e SCONE_HEAP=2G -e SCONE_ALLOW_DLOPEN=1 --entrypoint=""")
MRENCLAVE_MY_PRINT_DEFAULTS=$(get_mrenclave $MARIADB_IMAGE my_print_defaults "-e SCONE_HEAP=2G -e SCONE_ALLOW_DLOPEN=1 --entrypoint=""")
MRENCLAVE_MYSQL=$(get_mrenclave $MARIADB_IMAGE mysql "-e SCONE_HEAP=2G -e SCONE_ALLOW_DLOPEN=1 --entrypoint=""")
MRENCLAVE_SIMPLECLIENT_FASTAPISERVER=$(get_mrenclave $FASTAPISERVER_IMAGE python3 "-e SCONE_HEAP=2G -e SCONE_ALLOW_DLOPEN=2")
cat > /tmp/mrenclaves.sh << EOF
export CAS_MRENCLAVE="$CAS_MRENCLAVE"
export MRENCLAVE_MYSQLD="$MRENCLAVE_MYSQLD"
export MRENCLAVE_MYSQL="$MRENCLAVE_MYSQL"
export MRENCLAVE_MY_PRINT_DEFAULTS="$MRENCLAVE_MY_PRINT_DEFAULTS"
export MRENCLAVE_SIMPLECLIENT_FASTAPISERVER="$MRENCLAVE_SIMPLECLIENT_FASTAPISERVER"
EOF
sed 's/\r//g' /tmp/mrenclaves.sh > mrenclaves.sh
echo "OK."
#!/bin/bash
set -euo pipefail
export SCONE_CLI_CONFIG="~/.cas/dataowner-config.json"
# Random postfix.
POSTFIX=$RANDOM-$RANDOM-$RANDOM
# Define policy names, used in templates.
# Set name to "" to avoid the session submission.
export DB_SESSION="database_policy_$POSTFIX"
export DB_SIMPLECLIENT="database_simpleclient_$POSTFIX"
# MRENCLAVEs.
# Run `pull_latest_images.sh` to export the correct, up-to-date
# MRENCLAVES to your environment.
source "${BASH_SOURCE%/*}/mrenclaves.sh"
# Parse CAS address.
# If provided SCONE_CAS_ADDR is an IPv4 address,
# create an entry for "cas" in /etc/hosts with
# such address. This is needed because SCONE CLI
# does not support IP addresses when attesting a CAS.
# If the provided SCONE_CAS_ADDR is a name, just use it.
if [[ -z "$SCONE_CAS_ADDR" ]]; then
CAS_ADDR="cas"
elif [[ $SCONE_CAS_ADDR =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
# NOTE: checking only for a generic IPv4 format (with no octet validation).
CAS_ADDR="cas"
echo "$SCONE_CAS_ADDR $CAS_ADDR" >> /etc/hosts
else
CAS_ADDR=$SCONE_CAS_ADDR
fi
# Attest CAS.
# Attest CAS before uploading the session file, accept CAS running in debug
# mode (--only_for_testing-debug), outdated TCB (-G) and hyper-thread enabled (-C).
echo "Attesting CAS..."
scone cas attest -G -C --only_for_testing-debug --only_for_testing-ignore-signer --only_for_testing-trust-any "$CAS_ADDR" "$CAS_MRENCLAVE"
# Submit policies.
# SCONE CLI utility will substitute the variables based on your
# environment (the variables exported in the lines above).
# That means that you can also customize such policies by exporting
# extra variables and referencing them on the templates.
if [ ! -z "$DB_SIMPLECLIENT" ]; then
# Simple client parameters.
export DB_USER="scontain"
export DB_HOST="${RELEASE_NAME:-mariadb}-mariadb-scone" # Accepts names or IP addresses
export DB_PORT="3306"
export DB_DATABASE="test_db"
export TABLE="test_table"
echo "Uploading policy $DB_SIMPLECLIENT (MariaDB simple client)..."
scone session create --use-env "${BASH_SOURCE%/*}/db_simpleclient.yml"
echo ""
echo "export SIMPLE_CLIENT_CONFIG_ID="$DB_SIMPLECLIENT"" > "${BASH_SOURCE%/*}/myenv"
unset DB_USER
fi
if [ ! -z "$DB_SESSION" ]; then
# Server FSPF key and tag.
# This should be generated in advance, when building
# a MariaDB image with encrypted libs.
# Please refer to the documentation at github.com/scontain/scone-templates.
source "${BASH_SOURCE%/*}/fspf_variables.sh"
echo "Uploading policy $DB_SESSION (MariaDB server)..."
scone session create --use-env "${BASH_SOURCE%/*}/db_session.yml"
echo ""
echo "export DB_CONFIG_ID="$DB_SESSION"" >> "${BASH_SOURCE%/*}/myenv"
fi
echo "export SCONE_CAS_ADDR="$SCONE_CAS_ADDR"" >> "${BASH_SOURCE%/*}/myenv"
# Uncomment to double check submitted policies.
#scone session read $DB_SESSION
#scone session read $DB_SIMPLECLIENT
ARG BASE_IMAGE=sconecuratedimages/apps:mariadb-10.4-alpine-scone5.0.0
FROM sconecuratedimages/sconecli:alpine3.7-scone5.0.0 as cli
FROM $BASE_IMAGE as fspf
COPY --from=cli /opt/scone/bin/rust-cli /usr/local/bin/scone
COPY fspf.sh /
RUN SCONE_NO_FS_SHIELD=1 /fspf.sh
FROM $BASE_IMAGE
COPY --from=fspf /fspf.pb /
#!/bin/sh
set -x
scone fspf create fspf.pb
scone fspf addr fspf.pb / --not-protected --kernel /
scone fspf addr fspf.pb /usr/lib --authenticated --kernel /usr/lib
scone fspf addf fspf.pb /usr/lib /usr/lib
scone fspf encrypt fspf.pb
......@@ -24,28 +24,6 @@ connection = pymysql.connect(host=DB_HOST,
},
cusorclass=pymysql.cursors.DictCursor)
# configure database
try:
# create document database and table
with connection.cursor() as cursor:
sql = """
CREATE DATABASE IF NOT EXISTS document_db;
CREATE TABLE IF NOT EXISTS document_db.document(
record_id INT PRIMARY KEY,
content VARCHAR(1000) NOT NULL
);
"""
cursor.execute(sql)
connection.commit()
except pymysql.err as err:
print(err)
print("Error Code:", err.errno)
print("SQLSTATE", err.sqlstate)
print("Message", err.msg)
# FastAPI config
class Document(BaseModel):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment