Skip to content

Commit

Permalink
WASM: Add JWT policy
Browse files Browse the repository at this point in the history
This is a dirty commit POC around implementation of a policy, there are a lot
of hardcoded things, and these are the reason:

-> On a new filter on rust, no way to share metadata. So config should
be hardcoded, PR to change that in upstream is already open.
-> On JWT parsing, there is no way to get the value from Envoy, shared
metadata, etc.. so need to work on decode that, all the libraries are
using SSL libs, and WASM cannot be started if enabled.

Signed-off-by: Eloy Coto <[email protected]>
  • Loading branch information
eloycoto committed Nov 3, 2020
1 parent 31bfe4c commit 7d81471
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 5 deletions.
16 changes: 14 additions & 2 deletions compose/control-plane/config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
[
{
"id": 1,
"hosts": ["web", "web.app"],
"policies": [],
"hosts": [
"web",
"web.app"
],
"policies": [
{
"name": "jwt",
"configuration": {
"rules": [
"AA"
]
}
}
],
"target_domain": "web.app:80",
"proxy_rules": [
{
Expand Down
8 changes: 7 additions & 1 deletion src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,17 @@ pub struct MappingRules {
delta: u32,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct PoliciyConfig {
pub name: String,
configuration: serde_json::Value,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Service {
pub id: u32,
pub hosts: Vec<std::string::String>,
pub policies: Vec<std::string::String>,
pub policies: Vec<PoliciyConfig>,
pub target_domain: std::string::String,
pub proxy_rules: Vec<MappingRules>,
}
Expand Down
14 changes: 14 additions & 0 deletions wasm_filter/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions wasm_filter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ wasm-bindgen-macro = "0.2.60"
serde_json = "^1"
serde = { version = "^1", features = ["derive"] }

anyhow = "^1"
base64 = "0.13.0"

[lib]
crate-type = ["cdylib"]
9 changes: 8 additions & 1 deletion wasm_filter/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::cell::RefCell;
use std::collections::HashMap;

Expand Down Expand Up @@ -30,11 +31,17 @@ impl MappingRule {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct PoliciyConfig {
pub name: String,
configuration: serde_json::Value,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Service {
pub id: u32,
pub hosts: Vec<String>,
pub policies: Vec<String>,
pub policies: Vec<PoliciyConfig>,
pub target_domain: String,
pub proxy_rules: Vec<MappingRule>,
}
Expand Down
128 changes: 128 additions & 0 deletions wasm_filter/src/jwt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use proxy_wasm::traits::*;
use proxy_wasm::types::*;

use base64::decode;

#[derive(Debug, Default)]
pub struct Rules {
path: String,
claim: String, // Move to liquid
claim_value: String, // Move to liquid
allow: bool,
}
impl Rules {
pub fn matches(&self, path: String, jwt: PayloadType) -> bool {
log::info!("------------------------------------");
log::info!("------------------------------------");
if self.path != path {
return false;
}
match jwt.get(self.claim.as_str()) {
None => false,
Some(j) => {
// @TODO at some point of time, this should be a liquid policy.
if j.as_str().unwrap() == self.claim_value.as_str() {
return true;
}
false
}
}
}
}

#[derive(Debug, Default)]
pub struct JWTConfig {
pub rules: Vec<Rules>,
}

#[derive(Debug, Default)]
pub struct JWT {
pub context_id: u32,
pub config: JWTConfig,
}

pub type PayloadType = std::collections::HashMap<String, serde_json::Value>;

impl JWT {
pub fn new(context_id: u32) -> JWT {
JWT {
context_id,
config: JWTConfig {
..Default::default()
},
}
}

pub fn config(&mut self) {
self.config.rules = Vec::new();
self.config.rules.push(Rules {
path: "/headers".to_string(),
claim: "name".to_string(),
claim_value: "John Doe".to_string(),
allow: false,
});
}

pub fn get_jwt_token(&self) -> Result<PayloadType, anyhow::Error> {
// @TODO: This is a shit-show! Sorry!
// This should be changed in two different ways:
// 1) and for me, the best one, is to be able to read this property from envoy.
// https://github.com/envoyproxy/envoy/blob/bd73f3c4da0efffb2593d7c9ecf87788856dc052/source/extensions/filters/http/jwt_authn/filter.cc#L104
// 2) Use any decoder from any crate, and here we have the problems with SSL* things, that
// are not available on Envoy.
//
// as you can imagine, the verification happens on jwt_authn filter :-)
let raw_header = self.get_http_request_header("Authorization");
if raw_header.is_none() {
return Err(anyhow::Error::msg("Failed to get Bearer token"));
}

let s = raw_header.unwrap();
let result: Vec<_> = s.split_whitespace().collect();
if result.len() != 2 {
return Err(anyhow::Error::msg("Failed to extract bearer token"));
}

let decoded_token: Vec<_> = result.get(1).unwrap().split(".").collect();
let raw_payload = decode(decoded_token.get(1).unwrap())?;

let payload: PayloadType = serde_json::from_slice(raw_payload.as_slice())?;

return Ok(payload);
}

fn get_path(&self) -> Option<std::string::String> {
return self.get_http_request_header(":path");
}
}

impl Context for JWT {}

impl HttpContext for JWT {
fn on_http_request_headers(&mut self, _: usize) -> Action {
// @TODO to be removed until HTTP_CONTEXT can have metadata attached
self.config();

let jwt_token = self.get_jwt_token();
if jwt_token.is_err() {
log::warn!("Error on JWT auth: '{:?}'", jwt_token);
self.send_http_response(403, vec![], Some(b"Access forbidden.\n"));
return Action::Pause;
}
let token = jwt_token.unwrap();
let mut result = false;

for rule in &self.config.rules {
if rule.matches(self.get_path().unwrap(), token.clone()) {
result = true;
}
}

if result == true {
return Action::Continue;
}

self.send_http_response(403, vec![], Some(b"Access forbidden.\n"));
return Action::Pause;
}
}
12 changes: 11 additions & 1 deletion wasm_filter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use proxy_wasm::types::*;
use std::time::Duration;

mod config;
mod jwt;

const AUTH_BACKEND: &str = "httpbin";

Expand All @@ -28,7 +29,16 @@ impl Context for ConfigContext {}
impl RootContext for ConfigContext {
fn on_vm_start(&mut self, _: usize) -> bool {
let config = self.get_configuration();
config::import_config(std::str::from_utf8(&config.unwrap()).unwrap());

let service = config::import_config(std::str::from_utf8(&config.unwrap()).unwrap());
for policy in &service.policies {
if policy.name.as_str() == "jwt" {
let cb =
|context_id, _| -> Box<dyn HttpContext> { Box::new(jwt::JWT::new(context_id)) };
proxy_wasm::set_http_context(cb);
}
}

self.set_tick_period(Duration::from_secs(20));
true
}
Expand Down

0 comments on commit 7d81471

Please sign in to comment.