diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..fdc66830 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# ignore everything +** +# whitelist just the pubkey, used for verifying sigs +!sd-workstation/apt-test-pubkey.asc diff --git a/.gitignore b/.gitignore index 3ba97168..3a5e3625 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,7 @@ builder/packages/securedrop-workstation-grsec/debian/debhelper-build-stamp builder/packages/securedrop-workstation-grsec/debian/securedrop-workstation-grsec.substvars builder/packages/securedrop-workstation-grsec/debian/securedrop-workstation-grsec builder/packages/securedrop-workstation-grsec/debian/files + +# rpm package build artifacts +*.rpm +rpm-repo/ diff --git a/Makefile b/Makefile index c49b9ff5..46655846 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,9 @@ flake8: ## Lints all Python files with flake8 template: ## Builds securedrop-workstation Qube template RPM ./builder/build-workstation-template +publish-rpm: ## Uploads signed RPMs to dom0 repository + ./scripts/publish-rpm + prep-dom0: prep-salt # Copies dom0 config files for VM updates sudo qubesctl top.enable sd-vm-updates sudo qubesctl top.enable sd-dom0-files diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..aef7350f --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[dev-packages] + +[packages] +awscli = "*" + +[requires] +python_version = "3.5" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..3f9f3759 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,126 @@ +{ + "_meta": { + "hash": { + "sha256": "a201ff737d34f8ab732fa371c6207d5dfeacf3c06bfe473a48b72457e58c4d93" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.5" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "awscli": { + "hashes": [ + "sha256:00a438e74bfe7db2bfe4c0a7c91e87c2378133320e9f8b7ecd8f716557e74b0d", + "sha256:7c19110758c5a8a549548ba471e76ed21e62d27f427047a34b334b0c83eeae7b" + ], + "index": "pypi", + "version": "==1.16.140" + }, + "botocore": { + "hashes": [ + "sha256:128130b12f8ba4bf07a673b119135264060eb98f6a4a7419cbd1f2c6dc926827", + "sha256:59376112fdee707927b644dd44a1771861f8fe354a48d596131ced83d7a3c05b" + ], + "version": "==1.12.130" + }, + "colorama": { + "hashes": [ + "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", + "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + ], + "version": "==0.3.9" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "futures": { + "hashes": [ + "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265", + "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1" + ], + "markers": "python_version == '2.6' or python_version == '2.7'", + "version": "==3.2.0" + }, + "jmespath": { + "hashes": [ + "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", + "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" + ], + "version": "==0.9.4" + }, + "pyasn1": { + "hashes": [ + "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", + "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + ], + "version": "==0.4.5" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "markers": "python_version >= '2.7'", + "version": "==2.8.0" + }, + "pyyaml": { + "hashes": [ + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" + ], + "version": "==3.13" + }, + "rsa": { + "hashes": [ + "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", + "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" + ], + "version": "==3.4.2" + }, + "s3transfer": { + "hashes": [ + "sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e", + "sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021" + ], + "version": "==0.2.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "markers": "python_version == '2.7'", + "version": "==1.24.1" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index e6c2f443..4b6b22bd 100644 --- a/README.md +++ b/README.md @@ -304,15 +304,22 @@ Be aware that running tests *will* power down running SecureDrop VMs, and may re ## Building the Templates -1. Create a `fedora-29` AppVM for building -2. Increase the disk size to at least 15GB (as the build uses over 10GB) +1. Create a `fedora-29` AppVM for building the templates. It's going + to need Docker and several other packages every time you use it, so + it might be worth creating another template derived from + `fedora-29`, into which you can install those extras, and basing + the builder VM on that, or just using a StandaloneVM to save time + and repetition. +2. Increase the disk size to at least 15GB (as the build uses over + 10GB): `qvm-volume extend sd-template-builder:private 15GB` (if + your VM is not named `sd-template-builder`, adjust that command) 3. Import the QubesOS master key and the GPG key used to sign tags (see https://www.qubes-os.org/security/verifying-signatures/) 4. Run `make template` in the top-level of this repository. 5. Copy the rpm generated in `/home/user/src/securedrop-workstation/builder/qubes-builder/qubes-src/linux-template-builder/rpm/` to `dom0` 6. Install the template in `dom0` : `sudo rpm -i .rpm` (this takes a few minutes) 7. Create a new VM based on this template: ``` -qvm-create --template grsec-workstation test-grsec-kernels --class AppVM --property virt_mode=hvm --property kernel='' --label green +qvm-create --template securedrop-workstation test-securedrop-workstation --class AppVM --property virt_mode=hvm --property kernel='' --label green ``` ## Building workstation deb packages @@ -369,7 +376,7 @@ and `rpm -qi .rpm` will indicate an empty Signature field. Set up your env ``` sudo dnf install rpm-build rpm-sign # install required packages -echo "QUBES_GPG_DOMAIN=vault" | sudo tee /rw/config/gpg-split-domain # edit 'vault' as required +echo "vault" | sudo tee /rw/config/gpg-split-domain # edit 'vault' as required cat << EOF > ~/.rpmmacros %_signature gpg %_gpg_name @@ -382,16 +389,38 @@ Now we'll sign the RPM: ``` rpm --resign .rpm # --addsign would allow us to apply multiple signatures to the RPM rpm -qi # should now show that the file is signed -rpm -Kv # will complain that signature is not OK: "Digests SIGNATURES NOT OK" +rpm -Kv # should contain NOKEY errors in the lines containing Signature # This is because the the (public) key of the RPM signing key is not present, # and must be added to the RPM client config to verify the signature: sudo rpm --import .asc -rpm -Kv # will now say signatures are OK: "Digests signatures OK" +rpm -Kv # Signature lines will now contain OK instead of NOKEY ``` You can then proceed with distributing the package, via the "test" or "prod" repo, as appropriate. +## Distributing packages + +For the Debian packages, see https://github.com/freedomofpress/securedrop-debian-packaging/. +For the RPM packages, such as the `securedrop-workstation` TemplateVM package, first +build the package (e.g. `make template`), then sign the RPM, as outlined above. + +To upload the package to S3, you'll need valid AWS credentials. Talk to a member of the ops team. +Once you have valid credentials configured, install the dependencies (`pipenv install`), then run: + +``` +./scripts/publish-rpm +``` + +The RPM will immediately be available in dom0. Provided you've run the Salt configurations, +find it via: + +``` +sudo qubes-dom0-update --action=search qubes-template-securedrop-workstation +``` + +You can then install it directly. + ## Threat model This section outlines the threat model for the *SecureDrop Workstation*, and should complement [SecureDrop's threat model](https://docs.securedrop.org/en/stable/threat_model/threat_model.html). This document is always a work in progress, if you have any questions or comments, please open an issue on [GitHub](https://github.com/freedomofpress/securedrop-workstation) or send an email to [securedrop@freedom.press](mailto:securedrop@freedom.press). diff --git a/docker/CreateRepoRPM/Dockerfile b/docker/CreateRepoRPM/Dockerfile new file mode 100644 index 00000000..e8fc5488 --- /dev/null +++ b/docker/CreateRepoRPM/Dockerfile @@ -0,0 +1,12 @@ +# fedora:29 2019-04-15 +FROM fedora@sha256:8ee55e140e8751492ab2cfa4513c82093cd2716df9311ea6f442f1f1259cbb3e +LABEL maintainer="Freedom of the Press Foundation" +RUN dnf update -y && \ + dnf install -y \ + createrepo_c + +COPY sd-workstation/apt-test-pubkey.asc /tmp/apt-test-pubkey.asc +RUN rpm --import /tmp/apt-test-pubkey.asc + +RUN mkdir /repo +WORKDIR /repo diff --git a/dom0/sd-dom0-files.sls b/dom0/sd-dom0-files.sls index 9448bc03..dd4adfca 100644 --- a/dom0/sd-dom0-files.sls +++ b/dom0/sd-dom0-files.sls @@ -6,6 +6,68 @@ # over time. These scripts should be ported to an RPM package. ## +dom0-rpm-test-key: + file.managed: + # We write the pubkey to the repos config location, because the repos + # config location is automatically sent to dom0's UpdateVM. Otherwise, + # we must place the GPG key inside the fedora-29 TemplateVM, then + # restart sys-firewall. + - name: /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test + - source: "salt://sd/sd-workstation/apt-test-pubkey.asc" + - user: root + - group: root + - mode: 644 + +dom0-rpm-test-key-import: + cmd.run: + - name: sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test + - require: + - file: dom0-rpm-test-key + +dom0-rpm-test-key-sys-firewall: + cmd.run: + # Pass in the pubkey directly to sys-firewall, so it's available on the + # UpdateVM for dom0 while we're configuring dom0 repos. + - name: > + qvm-run -p sys-firewall ' + sudo tee /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test' + < /srv/salt/sd/sd-workstation/apt-test-pubkey.asc + +dom0-rpm-test-key-sys-firewall-import: + cmd.run: + - name: > + qvm-run --no-gui sys-firewall ' + sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test' + - require: + - cmd: dom0-rpm-test-key-sys-firewall + +dom0-workstation-rpm-repo: + # We use file.managed rather than pkgrepo.managed, because Qubes dom0 + # settings write new repos to /etc/yum.real.repos.d/, but only /etc/yum.repos.d/ + # is copied to the UpdateVM for fetching dom0 packages. + file.managed: + - name: /etc/yum.repos.d/securedrop-workstation-dom0.repo + - user: root + - group: root + - mode: 644 + - contents: | + [securedrop-workstation-dom0] + gpgcheck=1 + gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-securedrop-workstation-test + enabled=1 + baseurl=https://dev-bin.ops.securedrop.org/dom0-rpm-repo/ + name=SecureDrop Workstation Qubes dom0 repo + - require: + - file: dom0-rpm-test-key + +# Not installing automatically, since we have more testing to do +# dom0-install-securedrop-workstation-template: +# pkg.installed: +# - pkgs: +# - qubes-template-securedrop-workstation +# - require: +# - file: dom0-workstation-rpm-repo +# - cmd: dom0-rpm-test-key-sys-firewall # Copy script to system location so admins can run ad-hoc dom0-update-securedrop-script: diff --git a/rpm-repo/.empty b/rpm-repo/.empty new file mode 100644 index 00000000..e69de29b diff --git a/scripts/clone-to-dom0 b/scripts/clone-to-dom0 index da2edff7..29e2118e 100755 --- a/scripts/clone-to-dom0 +++ b/scripts/clone-to-dom0 @@ -25,6 +25,7 @@ function create-tarball() { printf "Cloning code from %s:%s ...\n" "${dev_vm}" "${dev_dir}" qvm-run --pass-io "$dev_vm" \ "tar -c --exclude-vcs \ + --exclude='*.rpm' \ -C '$(dirname "$dev_dir")' \ '$(basename "$dev_dir")'" > /tmp/sd-proj.tar } diff --git a/scripts/publish-rpm b/scripts/publish-rpm new file mode 100755 index 00000000..5a20a54b --- /dev/null +++ b/scripts/publish-rpm @@ -0,0 +1,65 @@ +#!/bin/bash +# Creates a local RPM repo, then pushes its contents to S3, for serving. +set -e +set -u +set -o pipefail + + +REPO_ROOT="$(git rev-parse --show-toplevel)" +RPM_LOCAL_DIR="${REPO_ROOT}/rpm-repo" + + +function list_rpms() { + find "$RPM_LOCAL_DIR" | grep '\.rpm$' +} + +function container_run() { + docker run --rm \ + --network=none \ + -v "$RPM_LOCAL_DIR:/repo" \ + fpf.local/createrepo \ + $@ +} + +# Check that we have local RPMs to build a repo for +if [[ -z "$(list_rpms)" ]]; then + echo "No RPM files found in $RPM_LOCAL_DIR" + echo "Build RPMs and place in that directory, then rerun the publish action." + exit 1 +fi + +# Ensure we have 'aws' installed, otherwise we cannot push to S3 +if ! hash aws > /dev/null 2>&1 ; then + echo "'aws' CLI not found, install requirements" + exit 2 +fi + +# Build container for preparing repo +docker build -t fpf.local/createrepo -f docker/CreateRepoRPM/Dockerfile . + +# TODO: In order to manage state over time, we'll need to *pull* from S3, +# populating the existing local dir with the current state of what's in S3. +# That's a bandwidth-intensive operation, so skipping for now. +# aws --profile sdpackager s3 sync "s3://dev-bin.ops.securedrop.org/dom0-rpm-repo/ ${RPM_LOCAL_DIR}/" + +# Sanity check that we have RPMs locally to upload, and they're already +# signed. +echo "Validating RPM signatures..." +while read -r f; do + fname="$(basename "$f")" + sig_results="$(container_run rpm -Kv "$fname")" + if ! grep -qP '^\s+V4 RSA/SHA256 Signature, key ID \w+: OK$' <<< "$sig_results"; then + echo "Failed to validate signature on $fname" + echo "Is the RPM signed? rpm -Kv showed:" + echo "$sig_results" + exit 3 + fi +done <<< "$(list_rpms)" + +# Use local container for creating repo metadata +container_run createrepo_c . + +# Push created repo dirtree to S3 +aws --profile sdpackager s3 sync \ + --exclude ".empty" \ + "${RPM_LOCAL_DIR}/" s3://dev-bin.ops.securedrop.org/dom0-rpm-repo/ diff --git a/sd-workstation/apt-test-pubkey.asc b/sd-workstation/apt-test-pubkey.asc index 2fa2b65f..b6c6ef9f 100644 --- a/sd-workstation/apt-test-pubkey.asc +++ b/sd-workstation/apt-test-pubkey.asc @@ -27,4 +27,4 @@ J7KQd1hEvVs00DxY6VlyK0FzXqByKYq6Arl2tzlCZ6RPEHKXV2xSP06jLEagzgYe DylVo9Xahenj4n/Mtq7Am6tGgU9Vy9cGbWNBdUND/mFQEEZSh9RJabPeluH12sir 5/tfsDr4DGHSz7ws+5M6Zbk6oNJEwQZ4cR+81qCfXE5X5LW1KlAL8wDl7dfS =fYUi ------END PGP PUBLIC KEY BLOCK----- \ No newline at end of file +-----END PGP PUBLIC KEY BLOCK-----