Skip to content

Commit c03fe11

Browse files
authored
Merge pull request #20 from pranav-bhatt/master
added JWTManipulator9000 to wasm-filters
2 parents 0a7e95d + be3e5d4 commit c03fe11

File tree

6 files changed

+324
-0
lines changed

6 files changed

+324
-0
lines changed

JWTManipulator9000/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "jwt-filter"
3+
version = "0.1.0"
4+
authors = ["pranav <adpranavb2000@gmail.com>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
[lib]
9+
crate-type = ["cdylib"]
10+
11+
[dependencies]
12+
base64 = "0.13.0"
13+
bincode = "1.0"
14+
proxy-wasm = "^0.1"
15+
serde = { version = "1.0", default-features = false, features = ["derive"] }
16+
serde_json ="1.0"
17+
wasm-bindgen = "0.2"

JWTManipulator9000/LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2021 The Layer5 Authors
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

JWTManipulator9000/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# JWTManipulator9000
2+
A WASM filter made to manipulate JWT token headers and payloads (currently only supports string parameters). Works best with the Meshery Project :)
3+
4+
DISCLAIMER: This filter doesn't regenerate the signature of the modified JWT, and provides no protections. Proceed with caution!
5+
6+
Sample configuration to be passed:
7+
```json
8+
{
9+
"add_header": [
10+
["header1","value1"],
11+
["header2","value2"]
12+
],
13+
"del_header":[
14+
"header1"
15+
],
16+
"add_payload": [
17+
["payload1","value1"],
18+
["payload2","value2"],
19+
],
20+
"del_payload":[
21+
"payload1"
22+
],
23+
"payload_to_header": [
24+
"payload2"
25+
],
26+
"header_to_payload": [
27+
"header2"
28+
]
29+
}
30+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod rules;
2+
3+
pub use rules::ConfigJwt;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use serde::Deserialize;
2+
3+
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)]
4+
pub struct ConfigJwt {
5+
pub add_header: Vec<(String, String)>,
6+
pub del_header: Vec<String>,
7+
pub add_payload: Vec<(String, String)>,
8+
pub del_payload: Vec<String>,
9+
pub payload_to_header: Vec<String>,
10+
pub header_to_payload: Vec<String>,
11+
}
12+
13+
impl ConfigJwt {
14+
pub fn new() -> Self {
15+
ConfigJwt {
16+
add_header: Vec::new(),
17+
add_payload: Vec::new(),
18+
del_header: Vec::new(),
19+
del_payload: Vec::new(),
20+
payload_to_header: Vec::new(),
21+
header_to_payload: Vec::new(),
22+
}
23+
}
24+
}

JWTManipulator9000/src/lib.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
mod json_parse;
2+
3+
use json_parse::ConfigJwt;
4+
use proxy_wasm::traits::*;
5+
use proxy_wasm::types::*;
6+
use serde::Deserialize;
7+
8+
use std::collections::HashMap;
9+
10+
// We need to make sure a HTTP root context is created and initialized when the filter is initialized.
11+
// The _start() function initialises this root context
12+
#[no_mangle]
13+
pub fn _start() {
14+
proxy_wasm::set_log_level(LogLevel::Info);
15+
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
16+
Box::new(UpstreamCallRoot {
17+
config_jwt: ConfigJwt::new(),
18+
})
19+
});
20+
}
21+
22+
// Defining standard CORS headers
23+
static CORS_HEADERS: [(&str, &str); 5] = [
24+
("Powered-By", "proxy-wasm"),
25+
("Access-Control-Allow-Origin", "*"),
26+
("Access-Control-Allow-Methods", "*"),
27+
("Access-Control-Allow-Headers", "*"),
28+
("Access-Control-Max-Age", "3600"),
29+
];
30+
31+
// This struct is what the JWT token sent by the user will deserialize to
32+
#[derive(Deserialize, Debug)]
33+
struct Jwt {
34+
headers: HashMap<String,String>,
35+
payload: HashMap<String,String>,
36+
}
37+
38+
impl Jwt {
39+
fn new() -> Self {
40+
Jwt {
41+
headers: HashMap::new(),
42+
payload: HashMap::new(),
43+
}
44+
}
45+
46+
fn add_header(&mut self, key: &String, value: &String) {
47+
self.headers.insert(key.clone(), value.clone());
48+
}
49+
50+
fn del_header(&mut self, key: &String) {
51+
self.headers.remove(key);
52+
}
53+
54+
fn add_payload(&mut self, key: &String, value: &String) {
55+
self.payload.insert(key.clone(), value.clone());
56+
}
57+
58+
fn del_payload(&mut self, key: &String) {
59+
self.payload.remove(key);
60+
}
61+
62+
fn payload_to_header(&mut self, key: &String, value: &String) {
63+
self.del_payload(key);
64+
self.add_header(key, value);
65+
}
66+
67+
fn header_to_payload(&mut self, key: &String, value: &String) {
68+
self.del_header(key);
69+
self.add_payload(key, value);
70+
}
71+
72+
// Wrapper function to run operations
73+
fn modify_jwt(&mut self, config: &ConfigJwt) {
74+
for (i,j ) in config.add_header.iter() {
75+
self.add_header(&i,&j);
76+
}
77+
78+
for i in config.del_header.iter() {
79+
self.del_header(&i);
80+
}
81+
82+
for (i,j ) in config.add_payload.iter() {
83+
self.add_payload(&i,&j);
84+
}
85+
86+
for i in config.del_payload.iter() {
87+
self.del_payload(&i);
88+
}
89+
proxy_wasm::hostcalls::log(LogLevel::Critical, format!("jwt: {:#?}",self).as_str())
90+
.ok();
91+
92+
for i in config.payload_to_header.iter() {
93+
proxy_wasm::hostcalls::log(LogLevel::Critical, format!("p2h: {}",i).as_str()).ok();
94+
let (key,value) = (i.clone(),self.payload.get(i).unwrap().clone());
95+
self.payload_to_header(&key,&value);
96+
}
97+
98+
for i in config.header_to_payload.iter() {
99+
let (key,value) = (i.clone(),self.headers.get(i).unwrap().clone());
100+
self.header_to_payload(&key.clone(), &value.clone());
101+
}
102+
}
103+
}
104+
105+
// This is the instance of a call made. It sorta derives from the root context
106+
#[derive(Debug)]
107+
struct UpstreamCall {
108+
config_jwt: ConfigJwt,
109+
final_jwt: String,
110+
}
111+
112+
impl UpstreamCall {
113+
// Takes in the HashMap created in the root context mapping path name to rule type
114+
fn new(jwt: &ConfigJwt) -> Self {
115+
Self {
116+
config_jwt: jwt.clone(),
117+
final_jwt: String::new(),
118+
}
119+
}
120+
}
121+
122+
impl Context for UpstreamCall {}
123+
124+
impl HttpContext for UpstreamCall {
125+
fn on_http_request_headers(&mut self, _num_headers: usize) -> Action {
126+
if let Some(method) = self.get_http_request_header(":method") {
127+
if method == "OPTIONS" {
128+
self.send_http_response(204, CORS_HEADERS.to_vec(), None);
129+
return Action::Pause;
130+
}
131+
}
132+
133+
134+
if let Some(jwt) = self.get_http_request_header("Authorization") {
135+
// Decoding JWT token
136+
let mut split_jwt: Vec<String> = jwt.splitn(3,".").map(|s| s.to_string()).collect();
137+
let (h, p) = (split_jwt[0].as_str(), split_jwt[1].as_str());
138+
let mut jwt = Jwt::new();
139+
140+
//proxy_wasm::hostcalls::log(LogLevel::Critical, format!("h: {},p:{}",h,p).as_str())
141+
// .ok();
142+
143+
//TODO: handle different types passed to json(modify config?)
144+
let b64_headers=base64::decode(h).unwrap();
145+
let b64_payload=base64::decode(p).unwrap();
146+
147+
//proxy_wasm::hostcalls::log(LogLevel::Critical, format!("h64: {:?},p64:{:?}",b64_headers,b64_payload).as_str())
148+
// .ok();
149+
150+
jwt.headers = serde_json::from_slice(&b64_headers).unwrap();
151+
jwt.payload = serde_json::from_slice(&b64_payload).unwrap();
152+
153+
//proxy_wasm::hostcalls::log(LogLevel::Critical, format!("Jwt: {:?}",jwt).as_str())
154+
// .ok();
155+
156+
jwt.modify_jwt(&self.config_jwt);
157+
158+
let mut b64_header = base64::encode(serde_json::to_string(&jwt.headers).unwrap());
159+
let mut b64_payload = base64::encode(serde_json::to_string(&jwt.payload).unwrap());
160+
161+
b64_header.pop();
162+
b64_header.pop();
163+
b64_payload.pop();
164+
b64_payload.pop();
165+
166+
split_jwt[0] = b64_header;
167+
split_jwt[1] = b64_payload;
168+
let new_jwt = split_jwt.join(".");
169+
170+
self.set_http_request_header("Authorization", Some(new_jwt.as_str()));
171+
172+
// Initialising headers to send back
173+
let mut headers = CORS_HEADERS.to_vec();
174+
175+
/*
176+
if false {
177+
self.send_http_response(
178+
429,
179+
headers,
180+
Some(b"Invalid plan name or duplicate plan names defined.\n"),
181+
);
182+
return Action::Pause;
183+
}
184+
*/
185+
186+
proxy_wasm::hostcalls::log(LogLevel::Debug, format!("jwt: {:?}", new_jwt).as_str())
187+
.ok();
188+
189+
headers.append(&mut vec![("jwt_test", new_jwt.as_str())]);
190+
self.send_http_response(200, headers, Some(b"OK\n"));
191+
return Action::Pause;
192+
}
193+
194+
self.send_http_response(401, CORS_HEADERS.to_vec(), Some(b"Unauthorized\n"));
195+
Action::Continue
196+
}
197+
198+
fn on_http_response_headers(&mut self, _num_headers: usize) -> Action {
199+
self.set_http_response_header("x-app-serving", Some("rate-limit-filter"));
200+
proxy_wasm::hostcalls::log(LogLevel::Debug, format!("RESPONDING").as_str()).ok();
201+
Action::Continue
202+
}
203+
}
204+
205+
struct UpstreamCallRoot {
206+
config_jwt: ConfigJwt,
207+
}
208+
209+
impl Context for UpstreamCallRoot {}
210+
impl<'a> RootContext for UpstreamCallRoot {
211+
//TODO: Revisit this once the read only feature is released in Istio 1.10
212+
// Get Base64 encoded JSON from envoy config file when WASM VM starts
213+
fn on_vm_start(&mut self, _: usize) -> bool {
214+
if let Some(config_bytes) = self.get_configuration() {
215+
// bytestring passed by VM -> String of base64 encoded JSON
216+
let config_str = String::from_utf8(config_bytes).unwrap();
217+
// String of base64 encoded JSON -> bytestring of decoded JSON
218+
let config_b64 = base64::decode(config_str).unwrap();
219+
// bytestring of decoded JSON -> String of decoded JSON
220+
let json_str = String::from_utf8(config_b64).unwrap();
221+
// Creating HashMap of pattern ("path name", "rule type") and saving into UpstreamCallRoot object
222+
self.config_jwt=serde_json::from_str(&json_str).unwrap();
223+
proxy_wasm::hostcalls::log(LogLevel::Critical, format!("config: {:#?}", self.config_jwt).as_str())
224+
.ok();
225+
}
226+
true
227+
}
228+
229+
fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
230+
// creating UpstreamCall object for each new call
231+
Some(Box::new(UpstreamCall::new(&self.config_jwt)))
232+
}
233+
234+
fn get_type(&self) -> Option<ContextType> {
235+
Some(ContextType::HttpContext)
236+
}
237+
}

0 commit comments

Comments
 (0)