Compare commits
No commits in common. "master" and "0.4.0" have entirely different histories.
26 changed files with 133 additions and 505 deletions
21
.drone.yml
21
.drone.yml
|
@ -24,26 +24,15 @@ steps:
|
||||||
image: clux/muslrust
|
image: clux/muslrust
|
||||||
commands:
|
commands:
|
||||||
- cargo build --release
|
- cargo build --release
|
||||||
- mkdir coffer-${DRONE_TAG}-x86_64-musl
|
- strip target/x86_64-unknown-linux-musl/release/coffer-server
|
||||||
- mv target/x86_64-unknown-linux-musl/release/coffer-server
|
- strip target/x86_64-unknown-linux-musl/release/coffer-client
|
||||||
target/x86_64-unknown-linux-musl/release/coffer-client
|
- strip target/x86_64-unknown-linux-musl/release/coffer-companion
|
||||||
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
|
- name: package
|
||||||
image: alpine
|
image: alpine
|
||||||
commands:
|
commands:
|
||||||
- tar cjf coffer-${DRONE_TAG}-x86_64-musl.tar.bz2
|
- 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
|
||||||
coffer-${DRONE_TAG}-x86_64-musl/coffer-server
|
- 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
|
||||||
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
|
- name: publish
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
|
|
4
.gitattributes
vendored
4
.gitattributes
vendored
|
@ -1,6 +1,2 @@
|
||||||
*.enc filter=lfs diff=lfs merge=lfs -text
|
*.enc filter=lfs diff=lfs merge=lfs -text
|
||||||
*.cert 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
|
|
||||||
coffer-client filter=lfs diff=lfs merge=lfs -text
|
|
||||||
coffer-server filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -88,7 +88,9 @@ dependencies = [
|
||||||
"exec 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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_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)",
|
"structopt 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
76
README.md
76
README.md
|
@ -1,77 +1 @@
|
||||||
# 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. 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.
|
|
||||||
|
|
||||||
|
|
27
TODO.org
Normal file
27
TODO.org
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#+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
|
|
@ -12,6 +12,8 @@ log = "0.4"
|
||||||
env_logger="0.7"
|
env_logger="0.7"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
# Communication
|
# Communication
|
||||||
|
serde = { version = "1.0", features = ["derive"]}
|
||||||
|
serde_yaml = "0.8"
|
||||||
serde_cbor = "0.10.2"
|
serde_cbor = "0.10.2"
|
||||||
# Executing subcommand
|
# Executing subcommand
|
||||||
exec = "0.3.1"
|
exec = "0.3.1"
|
||||||
|
|
|
@ -1,28 +1,20 @@
|
||||||
//! # 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)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use env_logger;
|
use env_logger;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use std:: {
|
use std:: {
|
||||||
net::TcpStream,
|
net::{SocketAddr, TcpStream},
|
||||||
error::Error,
|
error::Error,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
io::{Write, Read},
|
io::{Write, Read},
|
||||||
convert::{TryInto, TryFrom}
|
convert::{TryInto, TryFrom}
|
||||||
};
|
};
|
||||||
|
|
||||||
use coffer_common::{
|
use coffer_common::certificate::Certificate;
|
||||||
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)]
|
#[derive(StructOpt, Debug)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Address of the coffer server
|
/// Address of the coffer server
|
||||||
|
@ -83,8 +75,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
Err("Could not spawn sub-command".into())
|
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]) {
|
fn reap_coffer(cmd: &str, args: &[String]) {
|
||||||
let mut cmd = exec::Command::new(cmd);
|
let mut cmd = exec::Command::new(cmd);
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
//! A keypair container providing functionality for signing, encryption and
|
//! Common certificate handling and encryption
|
||||||
//! 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)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use std::{
|
use std::path::Path;
|
||||||
path::Path,
|
use std::io::BufReader;
|
||||||
io::BufReader,
|
use std::fs::File;
|
||||||
fs::File,
|
use std::fmt::{Debug, Formatter};
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::ops::Deref; // we use this but rustc doesn't know
|
|
||||||
|
|
||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
|
|
||||||
use seckey::SecKey;
|
use seckey::SecKey;
|
||||||
use sodiumoxide::crypto::{box_, sealedbox};
|
use sodiumoxide::crypto::box_;
|
||||||
|
use sodiumoxide::crypto::sealedbox;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use serde_cbor;
|
use serde_cbor;
|
||||||
|
|
||||||
|
@ -34,51 +25,22 @@ quick_error! {
|
||||||
Io(err: std::io::Error) {
|
Io(err: std::io::Error) {
|
||||||
from()
|
from()
|
||||||
}
|
}
|
||||||
SecKey {
|
SecKey
|
||||||
from(CertificateInner)
|
|
||||||
}
|
|
||||||
Crypto
|
Crypto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A secure container for a keypair
|
/// A secure container for certificates
|
||||||
///
|
///
|
||||||
/// Secure means a best effort approach to:
|
/// # Certificate
|
||||||
/// - Prevent swapping memory to disk
|
|
||||||
/// - Zeroing out memory upon dropping
|
|
||||||
/// - Prevent other processes and buffer overflows to access the secure memory
|
|
||||||
/// area
|
|
||||||
///
|
///
|
||||||
/// These guarantees are currently *not* reliable. If you threat model contains
|
/// A certificate consists of a public and a private key in a secure memory
|
||||||
/// targeted attacks against coffer memory, additional precautions have to be
|
/// area. With a certificate data sealed and opened.
|
||||||
/// taken.
|
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
inner: SecKey<CertificateInner>
|
inner: SecKey<CertificateInner>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {}
|
unsafe impl Send for Certificate {}
|
||||||
// After initialization, certificate is read-only
|
|
||||||
unsafe impl Sync for Certificate {}
|
unsafe impl Sync for Certificate {}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -87,8 +49,13 @@ struct CertificateInner {
|
||||||
private_key: box_::SecretKey
|
private_key: box_::SecretKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for CertificateInner {
|
||||||
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(fmt, "<Certificate Hidden>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Certificate {
|
impl Certificate {
|
||||||
/// Initialize with a generated keypair
|
|
||||||
pub fn new() -> Result<Certificate, CertificateError> {
|
pub fn new() -> Result<Certificate, CertificateError> {
|
||||||
debug!{"Generating new certificate"}
|
debug!{"Generating new certificate"}
|
||||||
let (public_key, private_key) = box_::gen_keypair();
|
let (public_key, private_key) = box_::gen_keypair();
|
||||||
|
@ -99,7 +66,6 @@ impl Certificate {
|
||||||
Ok(Certificate{inner})
|
Ok(Certificate{inner})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize from a serialized certificate in [cbor](https://cbor.io/) format
|
|
||||||
pub fn new_from_cbor<T: AsRef<Path>>(path: T) -> Result<Certificate, CertificateError> {
|
pub fn new_from_cbor<T: AsRef<Path>>(path: T) -> Result<Certificate, CertificateError> {
|
||||||
debug!{"Reading certificate from {}", path.as_ref().display()}
|
debug!{"Reading certificate from {}", path.as_ref().display()}
|
||||||
let f = File::open(path)?;
|
let f = File::open(path)?;
|
||||||
|
@ -110,35 +76,40 @@ impl Certificate {
|
||||||
Ok(Certificate{inner})
|
Ok(Certificate{inner})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize a certificate to a file in [cbor](https://cbor.io/) format
|
|
||||||
#[cfg(feature = "export")]
|
#[cfg(feature = "export")]
|
||||||
pub fn to_cbor(&self) -> Result<Vec<u8>, CertificateError> {
|
pub fn to_cbor(&self) -> Result<Vec<u8>, CertificateError> {
|
||||||
let read_guard = self.inner.read();
|
let inner_cert = &*self.inner.read();
|
||||||
|
let cbor = serde_cbor::to_vec(inner_cert)?;
|
||||||
let cbor = serde_cbor::to_vec(read_guard.deref())?;
|
|
||||||
|
|
||||||
Ok(cbor)
|
Ok(cbor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone the bytes of the public key
|
|
||||||
pub fn public_key(&self) -> Vec<u8> {
|
pub fn public_key(&self) -> Vec<u8> {
|
||||||
pk!(self).as_ref().to_owned()
|
self.inner.read().public_key.as_ref().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clone the bytes of the private key
|
|
||||||
#[cfg(feature = "export")]
|
#[cfg(feature = "export")]
|
||||||
pub fn secret_key(&self) -> Vec<u8> {
|
pub fn secret_key(&self) -> Vec<u8> {
|
||||||
sk!(self).as_ref().to_owned()
|
self.inner.read().private_key.as_ref().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a [sealed box](https://download.libsodium.org/doc/public-key_cryptography/sealed_boxes)
|
|
||||||
pub fn open(&self, c: &[u8]) -> Result<Vec<u8>, CertificateError> {
|
pub fn open(&self, c: &[u8]) -> Result<Vec<u8>, CertificateError> {
|
||||||
sealedbox::open(c, pk!{self}, sk!{self})
|
let pk = &self.inner.read().public_key;
|
||||||
|
let sk = &self.inner.read().private_key;
|
||||||
|
|
||||||
|
sealedbox::open(c, pk, sk)
|
||||||
.map_err(|_| CertificateError::Crypto)
|
.map_err(|_| CertificateError::Crypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seal a message in a [sealed box](https://download.libsodium.org/doc/public-key_cryptography/sealed_boxes)
|
|
||||||
pub fn seal(&self, message: &[u8]) -> Result<Vec<u8>, CertificateError> {
|
pub fn seal(&self, message: &[u8]) -> Result<Vec<u8>, CertificateError> {
|
||||||
Ok(sealedbox::seal(message, pk!{self}))
|
let pk = &self.inner.read().public_key;
|
||||||
|
|
||||||
|
Ok(sealedbox::seal(message, pk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T: AsRef<Path>> From<T> for Certificate {
|
||||||
|
fn from(path: T) -> Self {
|
||||||
|
Certificate::new_from_cbor(&path)
|
||||||
|
.expect(&format!{"Could not read certificate from {}", path.as_ref().display()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +1,17 @@
|
||||||
//! A storage container for client data
|
//! A storage container for client data
|
||||||
//!
|
|
||||||
//! Coffer supports separated client data by `CofferShard`s. Content of a shard
|
|
||||||
//! is a key-value store with typed values.
|
|
||||||
//!
|
|
||||||
//! # Coffer files
|
|
||||||
//! A `Coffer` can be read from a [toml](https://github.com/toml-lang/)
|
|
||||||
//! file in a specific format.
|
|
||||||
//!
|
|
||||||
//! ## Shards
|
|
||||||
//! A `CofferShard` is identified by a toml table with a field `id` containing
|
|
||||||
//! the unique shard id.
|
|
||||||
//!
|
|
||||||
//! Tables can be nested for grouping shards together. The grouping is not
|
|
||||||
//! necessarily reflected in the deserialized `Coffer`, as shards can be
|
|
||||||
//! uniquely identified by their id.
|
|
||||||
//!
|
|
||||||
//! Shards (tables with an id) cannot be nested.
|
|
||||||
//!
|
|
||||||
//! A simple shard with no data
|
|
||||||
//! ```toml
|
|
||||||
//! [app]
|
|
||||||
//! id = "1"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Grouped shard
|
|
||||||
//! ```toml
|
|
||||||
//! [app]
|
|
||||||
//! [app.frontend]
|
|
||||||
//! id = "1"
|
|
||||||
//!
|
|
||||||
//! [app.backend]
|
|
||||||
//! id = "2"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Nested shards (invalid)
|
|
||||||
//! ```toml
|
|
||||||
//! [app]
|
|
||||||
//! id = "1" # app is a shard since it has an id
|
|
||||||
//! [app.frontend] # invalid, can't nest shards inside other shards
|
|
||||||
//! id = "2"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## Values
|
|
||||||
//! Shards can contain a subset of toml values. The currently supported toml
|
|
||||||
//! values are:
|
|
||||||
//! - [String](https://github.com/toml-lang/toml#user-content-string)
|
|
||||||
//! - [Integer](https://github.com/toml-lang/toml#user-content-integer)
|
|
||||||
//! - [Float](https://github.com/toml-lang/toml#user-content-float)
|
|
||||||
//! - [Boolean](https://github.com/toml-lang/toml#user-content-boolean)
|
|
||||||
//!
|
|
||||||
//! ## Example
|
|
||||||
//! ```toml
|
|
||||||
//! [app]
|
|
||||||
//! [app.frontend]
|
|
||||||
//! id = "1"
|
|
||||||
//! password = "admin"
|
|
||||||
//! font_size = 1.4
|
|
||||||
//!
|
|
||||||
//! [app.backend]
|
|
||||||
//! id = "2"
|
|
||||||
//! cors = true
|
|
||||||
//!
|
|
||||||
//! [database]
|
|
||||||
//! id = "0"
|
|
||||||
//! user = "root"
|
|
||||||
//! passwort = "toor"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use std::{
|
use std::fmt::Debug;
|
||||||
fmt::Debug,
|
use std::path::Path;
|
||||||
fs::File,
|
use std::fs::File;
|
||||||
io::{BufReader, Read},
|
use std::io::{BufReader, Read};
|
||||||
path::Path,
|
|
||||||
};
|
use toml::Value as TomlValue;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
use toml::Value as TomlValue;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
quick_error! {
|
quick_error! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -96,7 +28,7 @@ quick_error! {
|
||||||
|
|
||||||
pub type CofferResult<T> = Result<T, CofferError>;
|
pub type CofferResult<T> = Result<T, CofferError>;
|
||||||
|
|
||||||
/// Values supported by `Coffer`
|
/// Values supported by a `Coffer`
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum CofferValue {
|
pub enum CofferValue {
|
||||||
/// A UTF-8 encoded string
|
/// A UTF-8 encoded string
|
||||||
|
@ -105,38 +37,35 @@ pub enum CofferValue {
|
||||||
Integer(i32),
|
Integer(i32),
|
||||||
/// A 32-bit float
|
/// A 32-bit float
|
||||||
Float(f32),
|
Float(f32),
|
||||||
/// A boolean value
|
// A boolean
|
||||||
Boolean(bool)
|
Boolean(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `CofferKey` defining the shard and the key into the kv-store
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
pub struct CofferKey {
|
pub struct CofferKey {
|
||||||
pub shard: String,
|
pub shard: String,
|
||||||
pub key: String
|
pub key: String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key-value store for client data
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct CofferShard(pub Vec<(String, CofferValue)>);
|
pub struct CofferShard(pub Vec<(String, CofferValue)>);
|
||||||
|
|
||||||
/// Trait for `Coffer`
|
/// Interface for interacting with a `Coffer`
|
||||||
pub trait Coffer {
|
pub trait Coffer {
|
||||||
/// Put `value` at `key`. Errors if there is already a value for `key`.
|
/// Put `value` at `path`. Errors if there is already a value at `path`.
|
||||||
fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()>;
|
fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()>;
|
||||||
|
|
||||||
/// Push `value` to `key`. Replaces existing values.
|
/// Push `value` to `path`. Replaces existing values.
|
||||||
fn push(&mut self, key: CofferKey, value: CofferValue);
|
fn push(&mut self, key: CofferKey, value: CofferValue);
|
||||||
|
|
||||||
/// Retrieve `value` at path. `None` if there is no `value` for `key`.
|
/// Retrieve `value` at path. Errors if there is no `value` at path.
|
||||||
fn get(&self, key: &CofferKey) -> Option<CofferValue>;
|
fn get(&self, key: &CofferKey) -> CofferResult<CofferValue>;
|
||||||
|
|
||||||
/// Retrieve an entire shard. `None` if there is no `CofferShard` for `shard`.
|
/// Retrieve `value` at path. Errors if there is no `value` at path.
|
||||||
fn get_shard<T>(&self, shard: T) -> Option<CofferShard>
|
fn get_shard<T>(&self, shard: T) -> CofferResult<CofferShard>
|
||||||
where T: AsRef<str>;
|
where T: AsRef<str>;
|
||||||
|
|
||||||
/// Deserializes a `Coffer` from a toml file
|
fn from_toml_file(toml_path: &Path) -> Self
|
||||||
fn from_toml_path(toml_path: &Path) -> Self
|
|
||||||
where Self: Coffer + Default
|
where Self: Coffer + Default
|
||||||
{
|
{
|
||||||
// read the secrets file into a temporary string
|
// read the secrets file into a temporary string
|
||||||
|
@ -206,5 +135,4 @@ pub trait Coffer {
|
||||||
self.put(CofferKey{shard, key}, value).unwrap();
|
self.put(CofferKey{shard, key}, value).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
//! Certificate of the keyring owner plus known and trusted public keys
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
@ -37,36 +36,28 @@ quick_error! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keyring container
|
|
||||||
///
|
|
||||||
/// A keyring constists of the owner's certificate and
|
|
||||||
/// the known and trusted public keys of the keyring owner
|
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
certificate: Certificate,
|
certificate: Certificate,
|
||||||
known_keys: HashMap<Vec<u8>, box_::PublicKey>
|
known_keys: HashMap<Vec<u8>, box_::PublicKey>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyring {
|
impl Keyring {
|
||||||
/// Create a new keyring
|
|
||||||
pub fn new(certificate: Certificate) -> Keyring {
|
pub fn new(certificate: Certificate) -> Keyring {
|
||||||
Keyring {
|
Keyring {
|
||||||
certificate,
|
certificate: certificate,
|
||||||
known_keys: HashMap::new()
|
known_keys: HashMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize a keyring from a file in [cbor](https://cbor.io) format
|
|
||||||
pub fn new_from_path<T>(certificate_path: T) -> Keyring
|
pub fn new_from_path<T>(certificate_path: T) -> Keyring
|
||||||
where T: AsRef<Path>
|
where T: AsRef<Path>
|
||||||
{
|
{
|
||||||
Keyring {
|
Keyring {
|
||||||
certificate: Certificate::new_from_cbor(certificate_path).unwrap(),
|
certificate: Certificate::from(certificate_path),
|
||||||
known_keys: HashMap::new()
|
known_keys: HashMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the table ids of a `Coffer` in toml format as known keys to the keyring
|
|
||||||
// TODO: This needs to be refactored. Keyring shouldn't be that tightly bound to coffer format
|
|
||||||
pub fn add_known_keys_toml(&mut self, toml: &str) -> Result<(), KeyringError> {
|
pub fn add_known_keys_toml(&mut self, toml: &str) -> Result<(), KeyringError> {
|
||||||
// parse the string into a toml Table
|
// parse the string into a toml Table
|
||||||
let clients: toml::value::Table = match toml.parse::<TomlValue>().unwrap() {
|
let clients: toml::value::Table = match toml.parse::<TomlValue>().unwrap() {
|
||||||
|
@ -109,21 +100,11 @@ impl Keyring {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a sealed message with the keyring owner's certificate
|
|
||||||
pub fn open(&self, message: &[u8]) -> Result<Vec<u8>, KeyringError> {
|
pub fn open(&self, message: &[u8]) -> Result<Vec<u8>, KeyringError> {
|
||||||
self.certificate.open(message)
|
self.certificate.open(message)
|
||||||
.map_err(KeyringError::from)
|
.map_err(KeyringError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seal a message for a client in the keyring
|
|
||||||
// TODO: Does this make sense?
|
|
||||||
// - What is a client in context of a keyring?
|
|
||||||
// - Why do we need to store trusted public keys in a keyring and not just
|
|
||||||
// encrypt for a pub key. Sealing does not need certificate.
|
|
||||||
// => We need authenticated encryption here. Sealed boxes can be tampered with by a MITM
|
|
||||||
// https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption
|
|
||||||
// I.e. a client could retrieve forged secrets by a attacker-controlled server, even if the
|
|
||||||
// real server's certificate was not exposed.
|
|
||||||
pub fn seal(&self, client: &[u8], message: &[u8]) -> Result<Vec<u8>, KeyringError> {
|
pub fn seal(&self, client: &[u8], message: &[u8]) -> Result<Vec<u8>, KeyringError> {
|
||||||
let client_key = self.known_keys.get(client)
|
let client_key = self.known_keys.get(client)
|
||||||
.ok_or(KeyringError::UnkownClientKey)?;
|
.ok_or(KeyringError::UnkownClientKey)?;
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
//! Common traits and function for coffer implementations
|
//! Common base for coffer binaries
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
pub mod certificate;
|
pub mod certificate;
|
||||||
pub mod coffer;
|
pub mod coffer;
|
||||||
pub mod keyring;
|
pub mod keyring;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub fn generate_key(out: PathBuf) {
|
||||||
let cert = certificate.to_cbor().unwrap();
|
let cert = certificate.to_cbor().unwrap();
|
||||||
|
|
||||||
let mut writer = File::create(&out)
|
let mut writer = File::create(&out)
|
||||||
.unwrap_or_else(|_| panic!{"Could not create out file {}", &out.display()});
|
.expect(&format!{"Could not create out file {}", &out.display()});
|
||||||
|
|
||||||
writer.write_all(&cert).unwrap();
|
writer.write_all(&cert).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::sync::RwLock;
|
||||||
use std::sync::RwLockReadGuard;
|
use std::sync::RwLockReadGuard;
|
||||||
use std::sync::RwLockWriteGuard;
|
use std::sync::RwLockWriteGuard;
|
||||||
|
|
||||||
use std::collections::hash_map::{HashMap, Entry};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use coffer_common::coffer::*;
|
use coffer_common::coffer::*;
|
||||||
|
|
||||||
|
@ -29,15 +29,13 @@ impl CofferMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Coffer for CofferMap {
|
impl Coffer for CofferMap {
|
||||||
fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()> {
|
fn put(&mut self, key: CofferKey, value: CofferValue) -> CofferResult<()> {
|
||||||
let mut lock = self.write();
|
let mut lock = self.write();
|
||||||
|
|
||||||
match lock.get_mut(&key.shard) {
|
match lock.get_mut(&key.shard) {
|
||||||
Some(shard) => {
|
Some(shard) => {
|
||||||
match shard.entry(key.key) {
|
if shard.contains_key(&key.key) { Err(CofferError::Msg("Key exists")) }
|
||||||
Entry::Occupied(_) => Err(CofferError::Msg("Key exists")),
|
else { shard.insert(key.key, value); Ok(()) }
|
||||||
Entry::Vacant(v) => { v.insert(value); Ok(()) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
lock.insert(key.shard.clone(), HashMap::new());
|
lock.insert(key.shard.clone(), HashMap::new());
|
||||||
|
@ -61,29 +59,33 @@ impl Coffer for CofferMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, key: &CofferKey) -> Option<CofferValue> {
|
fn get(&self, key: &CofferKey) -> CofferResult<CofferValue> {
|
||||||
let lock = self.read();
|
let lock = self.read();
|
||||||
|
|
||||||
lock.get(&key.shard)
|
let res = lock.get(&key.shard)
|
||||||
.and_then( |shard| { shard.get(&key.key) } )
|
.and_then( |shard| { shard.get(&key.key) } )
|
||||||
.map(|o| o.clone())
|
.ok_or(CofferError::Msg("Key not found"))?;
|
||||||
|
|
||||||
|
Ok(res.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shard<T>(&self, shard: T) -> Option<CofferShard>
|
fn get_shard<T>(&self, shard: T) -> CofferResult<CofferShard>
|
||||||
where T: AsRef<str>
|
where T: AsRef<str>
|
||||||
{
|
{
|
||||||
let lock = self.read();
|
let lock = self.read();
|
||||||
|
|
||||||
debug!{"Coffer {:?}", *lock}
|
debug!{"Coffer {:?}", *lock}
|
||||||
|
|
||||||
let map_to_vec = |map: &HashMap<String, CofferValue>| {
|
let coffer_shard = lock.get(shard.as_ref())
|
||||||
map.iter()
|
.ok_or(CofferError::Msg("Shard not found"))?;
|
||||||
.map(|(k,v)| (k.clone(), v.clone()))
|
|
||||||
.collect::<Vec<(String, CofferValue)>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
lock.get(shard.as_ref())
|
let mut res = CofferShard(Vec::new());
|
||||||
.and_then(|s| Some(CofferShard(map_to_vec(s))))
|
|
||||||
|
for (k,v) in coffer_shard {
|
||||||
|
res.0.push((k.clone(), v.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ where C: Coffer + Send + Sync + 'static
|
||||||
|
|
||||||
debug!{"Binding to socket {:?}", socket}
|
debug!{"Binding to socket {:?}", socket}
|
||||||
let mut listener = TcpListener::bind(socket).await
|
let mut listener = TcpListener::bind(socket).await
|
||||||
.expect("Could not bind to socket");
|
.expect(format!{"Could not bind to socket {}", socket}.as_str());
|
||||||
|
|
||||||
let server = async move {
|
let server = async move {
|
||||||
let mut incoming = listener.incoming();
|
let mut incoming = listener.incoming();
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
Here you can find a simple example on how to run coffer.
|
|
||||||
|
|
||||||
# Run the example
|
|
||||||
|
|
||||||
To run the example, simply execute:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
This should print out:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
@@@@@@@ @@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@
|
|
||||||
@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@
|
|
||||||
!@@ @@! @@@ @@! @@! @@! @@! @@@
|
|
||||||
!@! !@! @!@ !@! !@! !@! !@! @!@
|
|
||||||
!@! @!@ !@! @!!!:! @!!!:! @!!!:! @!@!!@!
|
|
||||||
!!! !@! !!! !!!!!: !!!!!: !!!!!: !!@!@!
|
|
||||||
:!! !!: !!! !!: !!: !!: !!: :!!
|
|
||||||
:!: :!: !:! :!: :!: :!: :!: !:!
|
|
||||||
::: ::: ::::: :: :: :: :: :::: :: :::
|
|
||||||
:: :: : : : : : : : :: :: : : :
|
|
||||||
|
|
||||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
||||||
TERM=xterm
|
|
||||||
container=podman
|
|
||||||
HOSTNAME=f7a1614d8752
|
|
||||||
HOME=/root
|
|
||||||
CLIENT_SECRET=SECRETKEY
|
|
||||||
client
|
|
||||||
0
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `CLIENT_SECRET` is the secret set into the environment of `printenv` in
|
|
||||||
the client. `printenv` is the coffer'ed process.
|
|
||||||
|
|
||||||
Note that if you connect to a shell in the client container you are not able to
|
|
||||||
retrieve the secret. The secret _only_ exists in the environment of the
|
|
||||||
coffer'ed process. It does not even exist in some parent process of the
|
|
||||||
coffer'ed process, as coffer reaps itself while starting the sub-process. This
|
|
||||||
is quite different from other alternatives that either set the secrets into the
|
|
||||||
environment of the container, or into a volume on the container. Both of which
|
|
||||||
are accessible (with more or less effort) from anyone that has access to the
|
|
||||||
container.
|
|
||||||
|
|
||||||
Also note how the whole server container, despite being able to service hundreds
|
|
||||||
of clients in parallel, uses less than 3 MB in total.
|
|
||||||
|
|
||||||
# Setup and Configuration
|
|
||||||
In this example we create a [coffer server](server/Dockerfile) with some [client secrets](config.toml)
|
|
||||||
and a simple [client container](client/Dockerfile). The container are built, run and connected a shared
|
|
||||||
network via [docker-compose](docker-compose.yml).
|
|
||||||
|
|
||||||
Both, the client and the server, need a certificate. The client for
|
|
||||||
authenticating with the server, and the server for decrypting the client
|
|
||||||
secrets.
|
|
||||||
|
|
||||||
Certificates can be generated with:
|
|
||||||
```shell
|
|
||||||
coffer-companion certificate certificate.cert
|
|
||||||
```
|
|
||||||
|
|
||||||
Furthermore, the secrets need to be authorized. Even though a coffer server can
|
|
||||||
handle secrets for multiple clients, a client can only request its own secrets.
|
|
||||||
Secrets retrieval is authorized by the public key of the client. You can get the
|
|
||||||
public key by
|
|
||||||
```shell
|
|
||||||
coffer-companion info certificate.cert
|
|
||||||
```
|
|
||||||
|
|
||||||
The public key must be put into the `id` section of a secret. For
|
|
||||||
example, our [secrets](config.toml) contain a section like this:
|
|
||||||
```toml
|
|
||||||
[client]
|
|
||||||
id = "452B0788D966059B21DB04FF37BC6161072B15EA2CDF88A5040FEEAB89D1143A"
|
|
||||||
CLIENT_SECRET = "SECRETKEY"
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, the promise of coffer is that certificates are the fundamental trust
|
|
||||||
anchor. This also means they are the _only_ thing you have to care about for
|
|
||||||
security of your secrets. Consequently, [secrets themselves are encrypted](server/config.enc)
|
|
||||||
with the server certificate:
|
|
||||||
```shell
|
|
||||||
coffer-companion encrypt --certificate certificate.cert --out config.enc --yaml config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
# Encoffering the client
|
|
||||||
You may have noticed that the actual client program (`printenv`) is not run
|
|
||||||
directly. Instead the [Dockerfile](client/Dockerfile) contains an entrypoint like this:
|
|
||||||
```Dockerfile
|
|
||||||
ENTRYPOINT ["coffer-client"]
|
|
||||||
|
|
||||||
CMD ["--certificate", "client.cert", \
|
|
||||||
"--server-address", "server:9187", \
|
|
||||||
"--", \
|
|
||||||
"printenv"]
|
|
||||||
```
|
|
||||||
|
|
||||||
The coffer client will connect to the server and retrieve its secrets.
|
|
||||||
Afterwards it sets the secrets into the process environment and replaces its own
|
|
||||||
process image with the coffer'ed command.
|
|
||||||
|
|
||||||
The major drawback of this approach is that you have to build your own images.
|
|
||||||
If you want to coffer your own software this is probably only a minor
|
|
||||||
inconvenience. For pre-build images like e.g. the official
|
|
||||||
[postgres](https://hub.docker.com/_/postgres) image this means you have to
|
|
||||||
derive your own image for and coffer the entrypoint. For postgres this might
|
|
||||||
look like:
|
|
||||||
```Dockerfile
|
|
||||||
FROM postgres:12-alpine
|
|
||||||
|
|
||||||
COPY ./coffer-client /usr/local/bin
|
|
||||||
COPY ./postgres.cert .
|
|
||||||
|
|
||||||
ENTRYPOINT ["coffer-client", \
|
|
||||||
"--certificate", "postgres.cert",
|
|
||||||
"--server-address", "server:9187",
|
|
||||||
"--",
|
|
||||||
"docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
CMD ["postgres"]
|
|
||||||
|
|
||||||
```
|
|
|
@ -1,13 +0,0 @@
|
||||||
FROM busybox
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/local/bin
|
|
||||||
|
|
||||||
COPY ./coffer-client /usr/local/bin
|
|
||||||
COPY ./client.cert .
|
|
||||||
|
|
||||||
ENTRYPOINT ["coffer-client"]
|
|
||||||
|
|
||||||
CMD ["--certificate", "client.cert", \
|
|
||||||
"--server-address", "server:9187", \
|
|
||||||
"--", \
|
|
||||||
"printenv"]
|
|
BIN
example/client/client.cert
(Stored with Git LFS)
BIN
example/client/client.cert
(Stored with Git LFS)
Binary file not shown.
BIN
example/client/coffer-client
(Stored with Git LFS)
BIN
example/client/coffer-client
(Stored with Git LFS)
Binary file not shown.
|
@ -1,3 +0,0 @@
|
||||||
[client]
|
|
||||||
id = "452B0788D966059B21DB04FF37BC6161072B15EA2CDF88A5040FEEAB89D1143A"
|
|
||||||
CLIENT_SECRET = "SECRETKEY"
|
|
|
@ -1,21 +0,0 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
server:
|
|
||||||
container_name: server
|
|
||||||
build:
|
|
||||||
context: ./server/
|
|
||||||
networks:
|
|
||||||
- coffer
|
|
||||||
|
|
||||||
client:
|
|
||||||
container_name: client
|
|
||||||
build:
|
|
||||||
context: ./client/
|
|
||||||
networks:
|
|
||||||
- coffer
|
|
||||||
depends_on:
|
|
||||||
- server
|
|
||||||
|
|
||||||
networks:
|
|
||||||
coffer:
|
|
|
@ -1,13 +0,0 @@
|
||||||
FROM gcr.io/distroless/static
|
|
||||||
|
|
||||||
COPY ./coffer-server .
|
|
||||||
COPY ./server.cert .
|
|
||||||
COPY ./config.enc .
|
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 9187
|
|
||||||
ENTRYPOINT ["./coffer-server"]
|
|
||||||
|
|
||||||
CMD ["--certificate", "server.cert", \
|
|
||||||
"--secrets", "config.enc", \
|
|
||||||
"--address", "0.0.0.0:9187"]
|
|
BIN
example/server/coffer-server
(Stored with Git LFS)
BIN
example/server/coffer-server
(Stored with Git LFS)
Binary file not shown.
BIN
example/server/config.enc
(Stored with Git LFS)
BIN
example/server/config.enc
(Stored with Git LFS)
Binary file not shown.
BIN
example/server/server.cert
(Stored with Git LFS)
BIN
example/server/server.cert
(Stored with Git LFS)
Binary file not shown.
BIN
overview.png
(Stored with Git LFS)
BIN
overview.png
(Stored with Git LFS)
Binary file not shown.
BIN
overview.svg
(Stored with Git LFS)
BIN
overview.svg
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in a new issue