Skip to content

Commit c6daa8b

Browse files
authored
Merge pull request #11 from customink/SimpleHashMap
Refactor to Simple HashMap. Docs. New Interface
2 parents bbe7092 + fbf5756 commit c6daa8b

File tree

11 files changed

+436
-506
lines changed

11 files changed

+436
-506
lines changed

Diff for: Cargo.lock

+185-336
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+3-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ crate_type = ["cdylib"]
1515
anyhow = "1.0.57"
1616
tokio = { version = "1.19.2", features = ["full"] }
1717
futures = { version = "0.3.21" }
18-
serde = { version = "1.0.137", features = ["derive"] }
19-
serde_json = "1.0.81"
20-
aws-config = "0.13.0"
21-
aws-sdk-ssm = "0.13.0"
22-
lambda_runtime = "0.5.1"
23-
lambda_extension = { git = "https://github.com/awslabs/aws-lambda-rust-runtime" }
18+
aws-config = "0.14.0"
19+
aws-sdk-ssm = "0.14.0"
20+
lambda-extension = "0.5.0"
2421
# lib
2522
redhook = "2.0"
2623
libc = "0.2.126"

Diff for: README.md

+98-21
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,115 @@
22

33
[![Actions Status](https://github.com/customink/crypteia/actions/workflows/test.yml/badge.svg)](https://github.com/customink/crypteia/actions/workflows/test.yml)
44

5-
# Crypteia
5+
# 🛡 Crypteia
66

7-
## Lambda Extension for Secure SSM Parameters as Environment Variables
7+
## Rust Lambda Extension for any Runtime to preload SSM Parameters as Secure Environment Variables!
88

9-
To-Do List:
9+
Super fast and only performaned once during your function's initialization, Crypteia turns your serverless YAML from this:
1010

11-
- Huge thanks to this guy! https://github.com/jakejscott/rust-parameters-lambda-extension/issues/1
12-
- Ensure lambda lifecycle events correct. Witnessed shutdown errors. Maybe we need some invoke hook to make sure SSM finished work?
13-
- Make sure dev containers work locally with VS Code Remote Development. Add docs below.
14-
- Set raw environment variables.
15-
- Maybe rename to something like cold environments?
11+
```yaml
12+
Environment:
13+
Variables:
14+
SECRET: x-crypteia-ssm:/myapp/SECRET
15+
```
1616
17-
## Development Environment
17+
Into real runtime (no matter the lang) environment variables backed by SSM Parameter Store. For example, assuming the SSM Parameter path above returns `1A2B3C4D5E6F` as the value. Your code would return:
1818

19-
Using Codespaces or VS Code Remote Containers...
19+
```javascript
20+
process.env.SECRET; // 1A2B3C4D5E6F
21+
```
2022

21-
- https://github.com/microsoft/vscode-remote-try-rust
22-
- https://github.com/microsoft/vscode-dev-containers/tree/main/containers/rust/history
23+
```ruby
24+
ENV['SECRET'] # 1A2B3C4D5E6F
25+
```
26+
27+
We do this using our lib via `LD_PRELOAD` with [redhook](https://github.com/geofft/redhook) in coordination with our [Lambda Extension](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html) binary. See installation & usage sections for more details.
28+
29+
💕 Many thanks to the following projects & people for their work, code, and personal help that made Crypteia possible:
30+
31+
- **[Hunter Madison](https://github.com/hmadison)**: Who taught me about how to use redhook based on Michele Mancioppi's [opentelemetry-injector](https://github.com/mmanciop/opentelemetry-injector) project.
32+
- **[Jake Scott](https://github.com/jakejscott)**: And his [rust-parameters-lambda-extension](https://github.com/jakejscott/rust-parameters-lambda-extension) project which served as the starting point for this project.
33+
34+
## Installation
35+
36+
🚧 🚧 🚧 TODO: Installation instructions for both `crypteia` binary extensions and `libcrypteia.so` file via `LD_PRELOAD`...
37+
38+
## Usage
39+
40+
First, you will need your secret environment variables setup in [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html). These can be whatever [hierarchy](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-hierarchies.html) you choose. Parameters can be any string type. However, we recommend using `SecureString` to ensure your secrets are encrypted within AWS. For example, let's assume the following paramter paths and values exists.
41+
42+
- `/myapp/SECRET` -> `1A2B3C4D5E6F`
43+
- `/myapp/access-key` -> `G7H8I9J0K1L2`
44+
- `/myapp/envs/DB_URL` -> `mysql2://u:p@host:3306`
45+
- `/myapp/envs/NR_KEY` -> `z6y5x4w3v2u1`
46+
47+
Crypteia supports two methods to fetch SSM parameters:
2348

24-
# Building
49+
1. `x-crypteia-ssm:` - Single path for a single environment variable.
50+
2. `x-crypteia-ssm-path:` - Path prefix to fetch many environment variables.
2551

26-
```sh
27-
./build.sh
52+
Using whatever serverless framework you prefer, setup your function's environment variables using either of the two SSM interfaces from above. For example, here is a environment variables section for an [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html) template that demonstrates all of Crypteia's features.
53+
54+
```yaml
55+
Environment:
56+
Variables:
57+
SECRET: x-crypteia-ssm:/myapp/SECRET
58+
ACCESS_KEY: x-crypteia-ssm:/myapp/access-key
59+
X_CRYPTEIA_SSM: x-crypteia-ssm-path:/myapp/envs
60+
DB_URL: x-crypteia
61+
NR_KEY: x-crypteia
2862
```
2963

30-
# Usage
64+
When your function initializes, each of the four environmet variables (`SECRET`, `ACCESS_KEY`, `DB_URL`, and `NR_KEY`) will return values from their respective SSM paths.
3165

32-
```shell
33-
FOO_PARAM=ssm_parameter:/my/parameter
34-
FOO_PARAM=my-parameter
3566
```
67+
process.env.SECRET; // 1A2B3C4D5E6F
68+
process.env.ACCESS_KEY; // G7H8I9J0K1L2
69+
process.env.DB_URL; // mysql2://u:p@host:3306
70+
process.env.NR_KEY; // z6y5x4w3v2u1
71+
```
72+
73+
Here are a few details about the internal implementation on how Crypteia works:
74+
75+
1. When accessing a single parameter path via `x-crypteia-ssm:` the environment variable name available to your runtime is used as is. No part of the parameter path effects the resulting name.
76+
2. When using `x-crypteia-ssm-path:` the environment variable name can be anything and the value is left unchanged.
77+
3. The parameter path hierarchy passed with `x-crypteia-ssm-path:` must be one level deep and end with valid environment variable names. These names must match environement placeholders using `x-crypteia` values.
78+
79+
For security, the usage of `DB_URL: x-crypteia` placeholders ensures that your application's configuration is in full control on which dynamic values can be used with `x-crypteia-ssm-path:`.
80+
81+
#### IAM Permissions
82+
83+
Please use AWS' [Restricting access to Systems Manager parameters using IAM policies](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html) guide for details on what policies your function's IAM Role will need. For an appliction to pull both single parameters as well as bulk paths, I have found the following policy helpful. It assumed the `/myapp` prefix and using AWS default KMS encryption key.
3684
37-
```shell
38-
FOO_PARAMS=ssm_parameters:/my/path/envs
85+
```json
86+
{
87+
"Version": "2012-10-17",
88+
"Statement": [
89+
{
90+
"Action": [
91+
"ssm:GetParameter",
92+
"ssm:GetParametersByPath",
93+
"ssm:GetParameters",
94+
"ssm:GetParameterHistory",
95+
"ssm:DescribeParameters"
96+
],
97+
"Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/myapp*",
98+
"Effect": "Allow"
99+
},
100+
{
101+
"Action": "kms:Decrypt",
102+
"Resource": "arn:aws:kms:us-east-1:123456789012:key/4914ec06-e888-4ea5-a371-5b88eEXAMPLE",
103+
"Effect": "Allow"
104+
}
105+
]
106+
}
39107
```
108+
109+
## Development
110+
111+
🚧 🚧 🚧 TODO: Talk more about Codespaces or VS Code Remote Containers...
112+
113+
- https://github.com/microsoft/vscode-remote-try-rust
114+
- https://github.com/microsoft/vscode-dev-containers/tree/main/containers/rust/history
115+
116+
🚧 🚧 🚧 TODO: Speak to commands and running tests.

Diff for: bin/test

-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ set -e
44
cargo test
55

66
./bin/build
7-
87
./test/libcrypteia.sh

Diff for: src/lib.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extern crate libc;
22
use libc::c_char;
3+
use std::collections::HashMap;
34
use std::ffi::CStr;
45
use std::ffi::CString;
56
use std::sync::Once;
@@ -19,16 +20,28 @@ redhook::hook! {
1920
println!("[crypteia] Initialized libcrypteia using LD_PRELOAD");
2021
}
2122
});
22-
let original_value = redhook::real!(getenv)(name);
23-
if original_value.is_null() {
24-
return original_value;
23+
let env_value = redhook::real!(getenv)(name);
24+
if env_value.is_null() {
25+
return env_value;
2526
}
26-
let given_name = CStr::from_ptr(name).to_str().unwrap();
27-
if given_name == "HELLO" {
28-
// https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.as_ptr
29-
let result = CString::new("WORLD").unwrap();
30-
return result.into_raw();
27+
let name_str = CStr::from_ptr(name).to_str().unwrap();
28+
let name_string = name_str.to_string();
29+
// TODO: Replace this with a shared data structure from binary.
30+
let crypteia_envs: HashMap<String, String> = HashMap::from([
31+
("SECRET".to_string(), "1A2B3C4D5E6F".to_string()),
32+
]);
33+
if crypteia_envs.contains_key(&name_string) {
34+
let env_value_str = CStr::from_ptr(env_value).to_str().unwrap();
35+
let env_value_string = env_value_str.to_string();
36+
if env_value_string.starts_with("x-crypteia") {
37+
let crypteia_value = crypteia_envs.get(&name_string).unwrap().clone();
38+
let crypteia_value_c_string = CString::new(crypteia_value).unwrap();
39+
crypteia_value_c_string.into_raw()
40+
} else {
41+
env_value
42+
}
43+
} else {
44+
env_value
3145
}
32-
original_value
3346
}
3447
}

Diff for: src/main.rs

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
mod ssm;
2-
use lambda_extension::{extension_fn, Error, LambdaEvent, NextEvent};
3-
use serde_json::json;
2+
use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent};
43
use std::collections::HashMap;
54

65
#[tokio::main]
76
async fn main() -> Result<(), Error> {
8-
println!("[crypteia] init");
9-
let vars: HashMap<String, String> = std::env::vars().collect();
10-
let config = aws_config::load_from_env().await;
11-
let ssm_client: aws_sdk_ssm::Client = aws_sdk_ssm::Client::new(&config);
12-
let parameters = ssm::fetch_parameters(vars, &ssm_client).await.unwrap();
13-
println!("[crypteia] fetched: {}", json!(&parameters));
14-
let func = extension_fn(parameters_extension);
7+
println!("[crypteia] Init");
8+
let env_vars: HashMap<String, String> = std::env::vars().collect();
9+
// TODO: Pass this data structure to the shared object library somehow.
10+
let _parameters = ssm::get_envs(env_vars).await.unwrap();
11+
println!("[crypteia] Fetched environment variables");
12+
let func = service_fn(parameters_extension);
1513
lambda_extension::run(func).await
1614
}
1715

0 commit comments

Comments
 (0)