Skip to content

Commit

Permalink
Additional documentation; rustfmt pass; extract stress test loop
Browse files Browse the repository at this point in the history
  • Loading branch information
int08h committed Oct 27, 2018
1 parent 388976d commit f84d4d7
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 143 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ YAML Key | Environment Variable | Necessity | Description
`seed` | `ROUGHENOUGH_SEED` | Required | A 32-byte hexadecimal value used to generate the server's long-term key pair. **This is a secret value and must be un-guessable**, treat it with care. (If compiled with KMS support, length will vary; see [Optional Features](#optional-features))
`batch_size` | `ROUGHENOUGH_BATCH_SIZE` | Optional | The maximum number of requests to process in one batch. All nonces in a batch are used to build a Merkle tree, the root of which is signed. Default is `64` requests per batch.
`status_interval` | `ROUGHENOUGH_STATUS_INTERVAL` | Optional | Number of _seconds_ between each logged status update. Default is `600` seconds (10 minutes).
`health_check_port` | `ROUGHENOUGH_HEALTH_CHECK_PORT` | Optional | If present, enable an HTTP health check responder on the provided port. Be careful with this, see [Optional Features](#optional-features).
`key_protection` | `ROUGHENOUGH_KEY_PROTECTION` | Optional | If compiled with KMS support, the ID of the KMS key used to protect the long-term identity. See [Optional Features](#optional-features).
`health_check_port` | `ROUGHENOUGH_HEALTH_CHECK_PORT` | Optional | If present, enable an HTTP health check responder on the provided port. **Use with caution**, see [Optional Features](#optional-features).
`kms_protection` | `ROUGHENOUGH_KMS_PROTECTION` | Optional | If compiled with KMS support, the ID of the KMS key used to protect the long-term identity. See [Optional Features](#optional-features).

#### YAML Configuration

Expand Down Expand Up @@ -147,14 +147,17 @@ Use Ctrl-C or `kill` the process.

## Optional Features

Roughenough has opt-in features enabled either A) via a config setting, or B) at compile-time.
Roughenough has two opt-in (disabled by default) features that are enabled either
A) via a config setting, or B) at compile-time.

* A simple HTTP health-check responder to facilitate detection and replacement
of "sick" Roughenough servers.
* Use of encryption and cloud Key Management Systems (KMS) to protect the
long-term server identity.
* [HTTP Health Check responder](doc/OPTIONAL-FEATURES.md#http-health-check)
to facilitate detection and replacement of "sick" Roughenough servers.
* [Key Management System (KMS) support](doc/OPTIONAL-FEATURES.md#key-management-system-kms-support)
to protect the long-term server identity using envelope encryption and
AWS or Google KMS.

See [OPTIONAL-FEATURES.md](doc/OPTIONAL-FEATURES.md) for details.
See [OPTIONAL-FEATURES.md](doc/OPTIONAL-FEATURES.md) for details and instructions
how to enable and use.


## Limitations
Expand Down
97 changes: 73 additions & 24 deletions doc/OPTIONAL-FEATURES.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Optional Features

These are opt-in features enabled either A) via a config setting, or B) at compile-time.
These features are **disabled by default** and must be explicitly enabled as
described below.

* [An HTTP Health Check responder](#http-health-check)
* [HTTP Health Check responder](#http-health-check)
* [Key Management System (KMS) support](#key-management-system-kms-support)


# HTTP Health Check

## Description

Intended for use by load balancers or other control plane facilities to monitor
the state of Roughenough servers and remove unhealthy instances automatically.

The server unconditionally emits a response to any TCP connection to the health
check port then closes the connection:
The server unconditionally emits a response to *any TCP connection* to the health
check port, then closes the connection:

```http
HTTP/1.1 200 OK
Expand All @@ -25,12 +25,19 @@ Connection: Close

No attempt is made to parse the request, the server *always* emits this response.

# How to enable
## How to enable

Provide a value for the `health_check_port` setting. This enables the HTTP
health check responder on the configured port.

Provide a value for the `health_check_port` setting. This enables the simplistic
HTTP health check responder on the configured port.
```yaml
interface: 127.0.0.1
port: 8686
seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3
health_check_port: 8000
```
## Warning
## DoS Warning
**An unprotected health-check port can be used to DoS the server. Do NOT expose
the health check port to the internet!**
Expand Down Expand Up @@ -98,12 +105,18 @@ projects/PROJECT_NAME/locations/GCP_LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KE

#### Credentials

Mercifully, the Rusoto library handles AWS credentials pretty smoothly as described in
[Rusoto's documentation](https://github.com/rusoto/rusoto/blob/master/AWS-CREDENTIALS.md).
[Rusoto](https://rusoto.org/) is used by Roughenough to access AWS. If your system
has AWS credentials in the typical `~/.aws/credentials` then everything should "just work".

#### Command line
Otherwise Rusoto supports alternative ways to provide AWS credentials. See
[Rusoto's documentation](https://github.com/rusoto/rusoto/blob/master/AWS-CREDENTIALS.md)
for details.

#### `roughenough-kms` Command line

```bash
# Provide AWS credentials as described in the Rusoto docs

# Build roughenough with AWS KMS support
$ cargo build --release --features "awskms"

Expand All @@ -113,13 +126,31 @@ $ target/release/roughenough-kms \
-s a0a31d76900080c3cdc42fe69de8dd0086d6b54de7814004befd0b9c4447757e

# Output of above will be something like this
key_protection: "arn:aws:kms:SOME_AWS_REGION:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
kms_protection: "arn:aws:kms:SOME_AWS_REGION:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
seed: b8000c000102020078d39e85c7386e9e2bed1f30fac6dd322db96b8aaac8974fc6c0e0f566f8f6c971012fca1e69fffffd947fe82a9e505baf580000007e307c06092a864886f70d010706a06f306d020100306806092a864886f70d010701301e060960864801650304012e3011040c55d16d891b3b2a1ae2587a9c020110803bcc74dd96336009087772b28ec908c40e4113b1ab9b98934bd3b4f3dd3c1e8cdc6da82a4321fd8378ad0e2e0507bf0c5ea0e28d447e5f8482533baa423b7af8459ae87736f381d87fe38c21a805fae1c25c43d59200f42cae0d07f741e787a04c0ad72774942dddf818be0767e4963fe5a810f734a0125c
```

Copy and paste the output `key_protection` and `seed` values into a config or
set the corresponding environment variables. `roughenough-server` will detect that
AWS KMS is being used and decrypt the seed automatically.
#### Configuration

Copy and paste the output `kms_protection` and `seed` values into a config or
set the corresponding environment variables. The `roughenough-server` will detect that
AWS KMS is being used and decrypt the seed automatically. For example:

```yaml
interface: 127.0.0.1
port: 8686
kms_protection: "arn:aws:kms:SOME_AWS_REGION:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
seed: b8000c000102020078d39e85c7386e9e2bed1f30fac6dd322db96b8aaac8974fc6c0e0f566f8f6c971012fca1e69fffffd947fe82a9e505baf580000007e307c06092a864886f70d010706a06f306d020100306806092a864886f70d010701301e060960864801650304012e3011040c55d16d891b3b2a1ae2587a9c020110803bcc74dd96336009087772b28ec908c40e4113b1ab9b98934bd3b4f3dd3c1e8cdc6da82a4321fd8378ad0e2e0507bf0c5ea0e28d447e5f8482533baa423b7af8459ae87736f381d87fe38c21a805fae1c25c43d59200f42cae0d07f741e787a04c0ad72774942dddf818be0767e4963fe5a810f734a0125c
```
or using environment based configuration:
```bash
$ export ROUGHENOUGH_INTERFACE=127.0.0.1
$ export ROUGHENOUGH_PORT=8686
$ export ROUGHENOUGH_KMS_PROTECTION="arn:aws:kms:SOME_AWS_REGION:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
$ export ROUGHENOUGH_SEED=b8000c000102020078d39e85c7386e9e2bed1f30fac6dd322db96b8aaac8974fc6c0e0f566f8f6c971012fca1e69fffffd947fe82a9e505baf580000007e307c06092a864886f70d010706a06f306d020100306806092a864886f70d010701301e060960864801650304012e3011040c55d16d891b3b2a1ae2587a9c020110803bcc74dd96336009087772b28ec908c40e4113b1ab9b98934bd3b4f3dd3c1e8cdc6da82a4321fd8378ad0e2e0507bf0c5ea0e28d447e5f8482533baa423b7af8459ae87736f381d87fe38c21a805fae1c25c43d59200f42cae0d07f741e787a04c0ad72774942dddf818be0767e4963fe5a810f734a0125c
```

### GCP Example

Expand All @@ -129,21 +160,22 @@ Only **Service Account credentials** (in `.json` format) are currently supported
GAE default credentials, and GCE default credentials are **not** supported (contributions to
add support are particularly welcome!).

To get Service Account credentials:
To obtain Service Account credentials if you don't already have them:

1. Creating a new service account?
* Creating a new service account?
1. Create the account
2. Download the credentials when prompted
2. Existing service account?
1. Open the Cloud Console https://console.cloud.google.com

* Existing service account?
1. Open the Cloud Console (https://console.cloud.google.com)
2. Navigate to `IAM -> Service accounts`
3. Locate the service account row, click on its "Actions" menu (the three dots on the right)
4. Choose `Create key` and `JSON` format
5. Download the credentials when prompted

Make note of the full path where the credentials are saved, it's needed in the next step.

#### Command line
#### `roughenough-kms` Command line

```bash
# Set environment variable pointing to downloaded Service Account credentials
Expand All @@ -158,11 +190,28 @@ $ target/release/roughenough-kms \
-s a0a31d76900080c3cdc42fe69de8dd0086d6b54de7814004befd0b9c4447757e

# Output of above will be something like this
key_protection: "projects/PROJECT_NAME/locations/GCP_LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME"
kms_protection: "projects/PROJECT_NAME/locations/GCP_LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME"
seed: 71000c000a2400c7f2553954873ef29aeb37384c25d7a937d389221207c3368657870129d601d084c8da1249008d6fd4640f815596788e97bb3ce02fd007bc25a1019ca51945c3b99283d3945baacd77b1b991f5f6f8848c549a5767f57c9c999e97fe6d28fdb17db1d63c2ea966d8236d20c71e8e9c757c5bab62472c65b48376bc8951700aceb22545fce58d77e7cc147f7134da7a2cca790b54f29e4798442cee6e0d34e57f80ce983f7e5928cceff2
```

Copy and paste the output `key_protection` and `seed` values into a config or
#### Configuration

Copy and paste the output `kms_protection` and `seed` values into a config or
set the corresponding environment variables. `roughenough-server` will detect that
Google KMS is being used and decrypt the seed automatically.
Google KMS is being used and decrypt the seed automatically. For example:

```yaml
interface: 127.0.0.1
port: 8686
kms_protection: "projects/PROJECT_NAME/locations/GCP_LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME"
seed: 71000c000a2400c7f2553954873ef29aeb37384c25d7a937d389221207c3368657870129d601d084c8da1249008d6fd4640f815596788e97bb3ce02fd007bc25a1019ca51945c3b99283d3945baacd77b1b991f5f6f8848c549a5767f57c9c999e97fe6d28fdb17db1d63c2ea966d8236d20c71e8e9c757c5bab62472c65b48376bc8951700aceb22545fce58d77e7cc147f7134da7a2cca790b54f29e4798442cee6e0d34e57f80ce983f7e5928cceff2
```
or using environment based configuration:
```bash
$ export ROUGHENOUGH_INTERFACE=127.0.0.1
$ export ROUGHENOUGH_PORT=8686
$ export ROUGHENOUGH_KMS_PROTECTION="projects/PROJECT_NAME/locations/GCP_LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME"
$ export ROUGHENOUGH_SEED=71000c000a2400c7f2553954873ef29aeb37384c25d7a937d389221207c3368657870129d601d084c8da1249008d6fd4640f815596788e97bb3ce02fd007bc25a1019ca51945c3b99283d3945baacd77b1b991f5f6f8848c549a5767f57c9c999e97fe6d28fdb17db1d63c2ea966d8236d20c71e8e9c757c5bab62472c65b48376bc8951700aceb22545fce58d77e7cc147f7134da7a2cca790b54f29e4798442cee6e0d34e57f80ce983f7e5928cceff2
```
45 changes: 25 additions & 20 deletions src/bin/roughenough-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::iter::Iterator;
use std::net::{ToSocketAddrs, UdpSocket};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};

use clap::{App, Arg};
use roughenough::merkle::root_from_paths;
use roughenough::sign::Verifier;
use roughenough::{RtMessage, Tag, CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT, roughenough_version};
use roughenough::{
roughenough_version, RtMessage, Tag, CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT,
};

fn create_nonce() -> [u8; 64] {
let rng = rand::SystemRandom::new();
Expand All @@ -61,6 +63,21 @@ fn receive_response(sock: &mut UdpSocket) -> RtMessage {
RtMessage::from_bytes(&buf[0..resp_len]).unwrap()
}

fn stress_test_forever(addr: &SocketAddr) -> ! {
if !addr.ip().is_loopback() {
panic!("Cannot use non-loopback address {} for stress testing", addr.ip());
}

println!("Stress testing!");

let nonce = create_nonce();
let socket = UdpSocket::bind("0.0.0.0:0").expect("Couldn't open UDP socket");
let request = make_request(&nonce);
loop {
socket.send_to(&request, addr).unwrap();
}
}

struct ResponseHandler {
pub_key: Option<Vec<u8>>,
msg: HashMap<Tag, Vec<u8>>,
Expand Down Expand Up @@ -162,7 +179,10 @@ impl ResponseHandler {

let hash = root_from_paths(index as usize, &self.nonce, paths);

assert_eq!(hash, srep[&Tag::ROOT], "Nonce is not present in the response's merkle tree");
assert_eq!(
hash, srep[&Tag::ROOT],
"Nonce is not present in the response's merkle tree"
);
}

fn validate_midpoint(&self, midpoint: u64) {
Expand Down Expand Up @@ -252,23 +272,7 @@ fn main() {
let addr = (host, port).to_socket_addrs().unwrap().next().unwrap();

if stress {
if !addr.ip().is_loopback() {
println!(
"ERROR: Cannot use non-loopback address {} for stress testing",
addr.ip()
);
return;
}

println!("Stress-testing!");

let nonce = create_nonce();
let socket = UdpSocket::bind("0.0.0.0:0").expect("Couldn't open UDP socket");
let request = make_request(&nonce);

loop {
socket.send_to(&request, addr).unwrap();
}
stress_test_forever(&addr)
}

let mut requests = Vec::with_capacity(num_requests);
Expand Down Expand Up @@ -317,3 +321,4 @@ fn main() {
);
}
}

4 changes: 2 additions & 2 deletions src/bin/roughenough-kms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) {

match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) {
Ok(encrypted_blob) => {
println!("key_protection: \"{}\"", kms_key);
println!("kms_protection: \"{}\"", kms_key);
println!("seed: {}", hex::encode(&encrypted_blob));
}
Err(e) => {
Expand All @@ -53,7 +53,7 @@ fn gcp_kms(kms_key: &str, plaintext_seed: &[u8]) {

match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) {
Ok(encrypted_blob) => {
println!("key_protection: \"{}\"", kms_key);
println!("kms_protection: \"{}\"", kms_key);
println!("seed: {}", hex::encode(&encrypted_blob));
}
Err(e) => {
Expand Down
2 changes: 1 addition & 1 deletion src/bin/roughenough-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ use std::sync::atomic::Ordering;

use roughenough::config;
use roughenough::config::ServerConfig;
use roughenough::server::Server;
use roughenough::roughenough_version;
use roughenough::server::Server;

macro_rules! check_ctrlc {
($keep_running:expr) => {
Expand Down
20 changes: 10 additions & 10 deletions src/config/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::time::Duration;

use config::ServerConfig;
use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL};
use key::KeyProtection;
use key::KmsProtection;
use Error;

///
Expand All @@ -33,7 +33,7 @@ use Error;
/// seed | `ROUGHENOUGH_SEED`
/// batch_size | `ROUGHENOUGH_BATCH_SIZE`
/// status_interval | `ROUGHENOUGH_STATUS_INTERVAL`
/// key_protection | `ROUGHENOUGH_KEY_PROTECTION`
/// kms_protection | `ROUGHENOUGH_KMS_PROTECTION`
/// health_check_port | `ROUGHENOUGH_HEALTH_CHECK_PORT`
///
pub struct EnvironmentConfig {
Expand All @@ -42,7 +42,7 @@ pub struct EnvironmentConfig {
seed: Vec<u8>,
batch_size: u8,
status_interval: Duration,
key_protection: KeyProtection,
kms_protection: KmsProtection,
health_check_port: Option<u16>,
}

Expand All @@ -51,7 +51,7 @@ const ROUGHENOUGH_INTERFACE: &str = "ROUGHENOUGH_INTERFACE";
const ROUGHENOUGH_SEED: &str = "ROUGHENOUGH_SEED";
const ROUGHENOUGH_BATCH_SIZE: &str = "ROUGHENOUGH_BATCH_SIZE";
const ROUGHENOUGH_STATUS_INTERVAL: &str = "ROUGHENOUGH_STATUS_INTERVAL";
const ROUGHENOUGH_KEY_PROTECTION: &str = "ROUGHENOUGH_KEY_PROTECTION";
const ROUGHENOUGH_KMS_PROTECTION: &str = "ROUGHENOUGH_KMS_PROTECTION";
const ROUGHENOUGH_HEALTH_CHECK_PORT: &str = "ROUGHENOUGH_HEALTH_CHECK_PORT";

impl EnvironmentConfig {
Expand All @@ -62,7 +62,7 @@ impl EnvironmentConfig {
seed: Vec::new(),
batch_size: DEFAULT_BATCH_SIZE,
status_interval: DEFAULT_STATUS_INTERVAL,
key_protection: KeyProtection::Plaintext,
kms_protection: KmsProtection::Plaintext,
health_check_port: None,
};

Expand Down Expand Up @@ -95,10 +95,10 @@ impl EnvironmentConfig {
cfg.status_interval = Duration::from_secs(u64::from(val));
};

if let Ok(key_protection) = env::var(ROUGHENOUGH_KEY_PROTECTION) {
cfg.key_protection = key_protection
if let Ok(kms_protection) = env::var(ROUGHENOUGH_KMS_PROTECTION) {
cfg.kms_protection = kms_protection
.parse()
.unwrap_or_else(|_| panic!("invalid key_protection value: {}", key_protection));
.unwrap_or_else(|_| panic!("invalid kms_protection value: {}", kms_protection));
}

if let Ok(health_check_port) = env::var(ROUGHENOUGH_HEALTH_CHECK_PORT) {
Expand Down Expand Up @@ -134,8 +134,8 @@ impl ServerConfig for EnvironmentConfig {
self.status_interval
}

fn key_protection(&self) -> &KeyProtection {
&self.key_protection
fn kms_protection(&self) -> &KmsProtection {
&self.kms_protection
}

fn health_check_port(&self) -> Option<u16> {
Expand Down
Loading

0 comments on commit f84d4d7

Please sign in to comment.