diff --git a/.drone.yml b/.drone.yml index 428c8c0..8c714f4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -24,15 +24,26 @@ steps: image: clux/muslrust commands: - cargo build --release - - strip target/x86_64-unknown-linux-musl/release/coffer-server - - strip target/x86_64-unknown-linux-musl/release/coffer-client - - strip target/x86_64-unknown-linux-musl/release/coffer-companion + - mkdir coffer-${DRONE_TAG}-x86_64-musl + - mv target/x86_64-unknown-linux-musl/release/coffer-server \ + target/x86_64-unknown-linux-musl/release/coffer-client \ + target/x86_64-unknown-linux-musl/release/coffer-companion \ + coffer-${DRONE_TAG}-x86_64-musl + - strip coffer-${DRONE_TAG}-x86_64-musl/coffer-server + - strip coffer-${DRONE_TAG}-x86_64-musl/coffer-client + - strip coffer-${DRONE_TAG}-x86_64-musl/coffer-companion - name: package image: alpine commands: - - tar cjf coffer-${DRONE_TAG}-x86_64-musl.tar.bz2 target/x86_64-unknown-linux-musl/release/coffer-server target/x86_64-unknown-linux-musl/release/coffer-client target/x86_64-unknown-linux-musl/release/coffer-companion - - tar czf coffer-${DRONE_TAG}-x86_64-musl.tar.gz target/x86_64-unknown-linux-musl/release/coffer-server target/x86_64-unknown-linux-musl/release/coffer-client target/x86_64-unknown-linux-musl/release/coffer-companion + - tar cjf coffer-${DRONE_TAG}-x86_64-musl.tar.bz2 \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-server \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-client \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-companion + - tar czf coffer-${DRONE_TAG}-x86_64-musl.tar.gz \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-server \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-client \ + coffer-${DRONE_TAG}-x86_64-musl/coffer-companion - name: publish image: plugins/gitea-release diff --git a/.gitattributes b/.gitattributes index 2c752ed..70e065a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ *.enc filter=lfs diff=lfs merge=lfs -text *.cert filter=lfs diff=lfs merge=lfs -text +overview.png filter=lfs diff=lfs merge=lfs -text +overview.svg filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index a0befe1..6caef01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,9 +88,7 @@ dependencies = [ "exec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/README.md b/README.md index c331c35..88dbf56 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ -# Coffer +# Coffer +[![Build Status](https://drone.friedl.net/api/badges/incubator/coffer/status.svg?ref=refs/heads/develop)](https://drone.friedl.net/incubator/coffer) + +Coffer is a collection of tools for the simple but secure management of +application configuration. + +It is meant to be flexible and simple, hence does not assume much about your +environment. Especially, you don't need a kubernetes cluster for running coffer. +Coffer runs directly on your server, just as well as in a containerized setup or +a full kubernetes cluster. The only thing Coffer needs is a TCP connection +between the `coffer-server` (securely holding your configuration) and the +`coffer-client` (retrieving configuration and setting up your application). + +## Overview +![](overview.png) + +## The Parts of Coffer +Coffer is split into 3 binaries and a supporting library: + +* `coffer-server`: The `coffer-server` securely stores configuration data and + hands them out to `coffer-clients` upon request +* `coffer-client`: A `coffer-client` requests configuration data from a + `coffer-server`, configures the application and may also start it up +* `coffer-companion`: A helper for generating certificates and encrypting + configuration data +* `coffer-common`: A common library for all binaries containing common + cryptographic operations, protocol code and interface definitions + +## Security +Coffer does not rely on a secure connection or any specifics of the environment. +Instead security is provided by a basic public key cryptography scheme. This +gives you, the user, the flexibility to set up your ecosystem according to your +own security needs. + +### Certificates +Certificates in coffer are the just a keypair consisting of a public and private +key as used by public key cryptography. So, basically, a certificate is nothing +more than a tuple of two primes. + +Certificates in coffer can be generated by the `coffer-companion`. Every +`coffer-server` and `coffer-client` need their own certificate. Security +in coffer squarely depends on these certificates being kept secret. + +### Configuration +Configuration can be written in [toml](https://github.com/toml-lang/toml) +format. It is secured by encrypting it with the public key of the +`coffer-server`. This can be done by invoking the `coffer-companion`. + +Encrypted configuration can be conveniently stored in VCS (e.g. via git-lfs) +with your application. As long as the server certificate stays private. + +### Communication +The communication between a `coffer-server` and a `coffer-client` is not and +does not need to be secured. A `coffer-server` associates configuration data +with the public keys of `coffer-clients`. Upon request of configuration data the +server sends back the configuration encrypted with the clients public key. Only +a client in posession of the corresponding private key can read the response. + +### Trust Anchors +It is worth mentioning some things about trust anchors. Every cryptography +scheme, no matter how sophisticated, needs, at some point, something that can be +trusted. In case of HTTPS this is provided by central certificate authorities. +In case of the encrypted letters to your best friend this might be a pre-shared +password transmitted over phone in 1972. + +From a coffer perspective keys can be trusted. That is, coffer assumes that +certificates are distributed and kept secret according to your threat model. An +attacker in control of a certificate can steal secret configuration! + +Coffer does not assume a trust anchor for you. Instead, you are free to choose +your own trust anchor according. In a simple personal server setup this might +mean just distributing certificates by hand. In a more complex, corporate +environment you may want to set up a secure, central authority. + +Trust anchors are a trade-off between convenience, complexity and security. +Coffer lets _you_ choose where along these axis you put your trust anchor. + diff --git a/TODO.org b/TODO.org deleted file mode 100644 index c659c65..0000000 --- a/TODO.org +++ /dev/null @@ -1,27 +0,0 @@ -#+TODO: TODO NEXT DONE - -* General -** TODO Add a license -** TODO Better communication protocol -** TODO Add tests -** TODO Readme -* Coffer Server -** TODO Double delete if files not kept -** TODO Add secrets on-the-fly -** TODO Store secrets in secure memory - - Not persisted - - Nulled out - - Optional encrypted -* Coffer Client -** DONE Set environment variables - CLOSED: [2019-11-27 Wed 22:51] -** TODO Send key requests encrypted/signed -** TODO Secure Communication -* Coffer Companion -** TODO Add Subcommands - - [X] Generate certificate - - [ ] Seal secrets with certificate - - [ ] Open secrets with certificate - - [ ] Generate trampolin sh from dockerfile -* Docker -** TODO Create Dockerfile for server diff --git a/coffer-client/Cargo.toml b/coffer-client/Cargo.toml index a4989f3..e0c56d1 100644 --- a/coffer-client/Cargo.toml +++ b/coffer-client/Cargo.toml @@ -12,8 +12,6 @@ log = "0.4" env_logger="0.7" structopt = "0.3" # Communication -serde = { version = "1.0", features = ["derive"]} -serde_yaml = "0.8" serde_cbor = "0.10.2" # Executing subcommand exec = "0.3.1" diff --git a/coffer-client/src/main.rs b/coffer-client/src/main.rs index 6d215bd..ff8b937 100644 --- a/coffer-client/src/main.rs +++ b/coffer-client/src/main.rs @@ -1,20 +1,28 @@ +//! # Coffer client +//! +//! Retrieve a secret shard from a `coffer-server`. Secrets in the shard are set +//! as environment variables for the spawned subcommand `cmd`. + #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; use env_logger; -use structopt::StructOpt; - use std:: { - net::{SocketAddr, TcpStream}, + net::TcpStream, error::Error, path::PathBuf, io::{Write, Read}, convert::{TryInto, TryFrom} }; -use coffer_common::certificate::Certificate; -use coffer_common::coffer::{CofferShard, CofferValue}; +use coffer_common::{ + coffer::{CofferShard, CofferValue}, + certificate::Certificate +}; +use structopt::StructOpt; + +/// Client for setting up the environment from coffer server secrets #[derive(StructOpt, Debug)] struct Args { /// Address of the coffer server @@ -75,6 +83,8 @@ fn main() -> Result<(), Box> { Err("Could not spawn sub-command".into()) } +/// Replaces the `coffer-client` process image with +/// the subcommand `cmd` with `args` fn reap_coffer(cmd: &str, args: &[String]) { let mut cmd = exec::Command::new(cmd); diff --git a/coffer-common/src/certificate.rs b/coffer-common/src/certificate.rs index 893d653..01df7b5 100644 --- a/coffer-common/src/certificate.rs +++ b/coffer-common/src/certificate.rs @@ -1,18 +1,27 @@ -//! Common certificate handling and encryption - +//! A keypair container providing functionality for signing, encryption and +//! decryption +//! +//! # Base libraries +//! The cryptographic operations exposed by this module are based on the +//! [NaCl](http://nacl.cr.yp.to/) fork [libsodium](https://libsodium.org) as +//! exposed by the rust bindings [sodiumoxide](https://crates.io/crates/sodiumoxide). +//! #[allow(unused_imports)] use log::{debug, error, info, trace, warn}; -use std::path::Path; -use std::io::BufReader; -use std::fs::File; -use std::fmt::{Debug, Formatter}; +use std::{ + path::Path, + io::BufReader, + fs::File, +}; + +#[allow(unused_imports)] +use std::ops::Deref; // we use this but rustc doesn't know use quick_error::quick_error; use seckey::SecKey; -use sodiumoxide::crypto::box_; -use sodiumoxide::crypto::sealedbox; +use sodiumoxide::crypto::{box_, sealedbox}; use serde::{Serialize, Deserialize}; use serde_cbor; @@ -25,22 +34,51 @@ quick_error! { Io(err: std::io::Error) { from() } - SecKey + SecKey { + from(CertificateInner) + } Crypto } } -/// A secure container for certificates +/// A secure container for a keypair /// -/// # Certificate +/// Secure means a best effort approach to: +/// - Prevent swapping memory to disk +/// - Zeroing out memory upon dropping +/// - Prevent other processes and buffer overflows to access the secure memory +/// area /// -/// A certificate consists of a public and a private key in a secure memory -/// area. With a certificate data sealed and opened. +/// These guarantees are currently *not* reliable. If you threat model contains +/// targeted attacks against coffer memory, additional precautions have to be +/// taken. pub struct Certificate { inner: SecKey } +// The SecKeyReadGuard prevents convenience methods for handing out references +// to private/public keys (reference outlives SecKeyReadGuard). Hence below +// macros are shortcut projections that can be used after a read guard is +// created + +// Get the public key +macro_rules! pk { + ($cert:ident) => { + &$cert.inner.read().public_key + }; +} + +// Get the private key +macro_rules! sk { + ($cert:ident) => { + &$cert.inner.read().private_key + }; +} + +// Certificate and its inner SecKey own their +// raw pointer without any thread local behaviour unsafe impl Send for Certificate {} +// After initialization, certificate is read-only unsafe impl Sync for Certificate {} #[derive(Serialize, Deserialize)] @@ -49,13 +87,8 @@ struct CertificateInner { private_key: box_::SecretKey } -impl Debug for CertificateInner { - fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { - write!(fmt, "