Skip to content

Commit c259bd8

Browse files
authored
add authorization_source feature (awslabs#478)
* add authorization_source feature * use option
1 parent c2b40f6 commit c259bd8

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ The readiness check port/path and traffic port can be configured using environme
104104
| AWS_LWA_ENABLE_COMPRESSION | enable gzip compression for response body | "false" |
105105
| AWS_LWA_INVOKE_MODE | Lambda function invoke mode: "buffered" or "response_stream", default is "buffered" | "buffered" |
106106
| AWS_LWA_PASS_THROUGH_PATH | the path for receiving event payloads that are passed through from non-http triggers | "/events" |
107+
| AWS_LWA_AUTHORIZATION_SOURCE | a header name to be replaced to `Authorization` | None |
107108

108109
> **Note:**
109110
> We use "AWS_LWA_" prefix to namespacing all environment variables used by Lambda Web Adapter. The original ones will be supported until we reach version 1.0.
@@ -134,6 +135,8 @@ Please check out [FastAPI with Response Streaming](examples/fastapi-response-str
134135

135136
**AWS_LWA_PASS_THROUGH_PATH** - Path to receive events payloads passed through from non-http event triggers. The default is "/events".
136137

138+
**AWS_LWA_AUTHORIZATION_SOURCE** - When set, Lambda Web Adapter replaces the specified header name to `Authorization` before proxying a request. This is useful when you use Lambda function URL with [IAM auth type](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html), which reserves Authorization header for IAM authentication, but you want to still use Authorization header for your backend apps. This feature is disabled by default.
139+
137140
## Request Context
138141

139142
**Request Context** is metadata API Gateway sends to Lambda for a request. It usually contains requestId, requestTime, apiId, identity, and authorizer. Identity and authorizer are useful to get client identity for authorization. API Gateway Developer Guide contains more details [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format).

src/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub struct AdapterOptions {
7777
pub async_init: bool,
7878
pub compression: bool,
7979
pub invoke_mode: LambdaInvokeMode,
80+
pub authorization_source: Option<String>,
8081
}
8182

8283
impl Default for AdapterOptions {
@@ -114,6 +115,7 @@ impl Default for AdapterOptions {
114115
.unwrap_or("buffered".to_string())
115116
.as_str()
116117
.into(),
118+
authorization_source: env::var("AWS_LWA_AUTHORIZATION_SOURCE").ok(),
117119
}
118120
}
119121
}
@@ -131,6 +133,7 @@ pub struct Adapter<C, B> {
131133
path_through_path: String,
132134
compression: bool,
133135
invoke_mode: LambdaInvokeMode,
136+
authorization_source: Option<String>,
134137
}
135138

136139
impl Adapter<HttpConnector, Body> {
@@ -167,6 +170,7 @@ impl Adapter<HttpConnector, Body> {
167170
ready_at_init: Arc::new(AtomicBool::new(false)),
168171
compression: options.compression,
169172
invoke_mode: options.invoke_mode,
173+
authorization_source: options.authorization_source.clone(),
170174
}
171175
}
172176
}
@@ -312,6 +316,14 @@ impl Adapter<HttpConnector, Body> {
312316
HeaderName::from_static("x-amzn-lambda-context"),
313317
HeaderValue::from_bytes(serde_json::to_string(&lambda_context)?.as_bytes())?,
314318
);
319+
320+
if let Some(authorization_source) = self.authorization_source.as_deref() {
321+
if req_headers.contains_key(authorization_source) {
322+
let original = req_headers.remove(authorization_source).unwrap();
323+
req_headers.insert("authorization", original);
324+
}
325+
}
326+
315327
let mut app_url = self.domain.clone();
316328
app_url.set_path(path);
317329
app_url.set_query(parts.uri.query());

tests/integ_tests/main.rs

+45
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ fn test_adapter_options_from_env() {
4141
env::set_var("AWS_LWA_TLS_SERVER_NAME", "api.example.com");
4242
env::remove_var("AWS_LWA_TLS_CERT_FILE");
4343
env::set_var("AWS_LWA_INVOKE_MODE", "buffered");
44+
env::set_var("AWS_LWA_AUTHORIZATION_SOURCE", "auth-token");
4445

4546
// Initialize adapter with env options
4647
let options = AdapterOptions::default();
@@ -55,6 +56,7 @@ fn test_adapter_options_from_env() {
5556
assert!(options.async_init);
5657
assert!(options.compression);
5758
assert_eq!(LambdaInvokeMode::Buffered, options.invoke_mode);
59+
assert_eq!(Some("auth-token".into()), options.authorization_source);
5860
}
5961

6062
#[test]
@@ -69,6 +71,7 @@ fn test_adapter_options_from_namespaced_env() {
6971
env::set_var("AWS_LWA_ASYNC_INIT", "true");
7072
env::set_var("AWS_LWA_ENABLE_COMPRESSION", "true");
7173
env::set_var("AWS_LWA_INVOKE_MODE", "response_stream");
74+
env::set_var("AWS_LWA_AUTHORIZATION_SOURCE", "auth-token");
7275

7376
// Initialize adapter with env options
7477
let options = AdapterOptions::default();
@@ -84,6 +87,7 @@ fn test_adapter_options_from_namespaced_env() {
8487
assert!(options.async_init);
8588
assert!(options.compression);
8689
assert_eq!(LambdaInvokeMode::ResponseStream, options.invoke_mode);
90+
assert_eq!(Some("auth-token".into()), options.authorization_source);
8791
}
8892

8993
#[test]
@@ -607,6 +611,47 @@ async fn test_http_content_encoding_suffix() {
607611
assert_eq!(json_data.to_owned(), body_to_string(response).await);
608612
}
609613

614+
#[tokio::test]
615+
async fn test_http_authorization_source() {
616+
// Start app server
617+
let app_server = MockServer::start();
618+
let hello = app_server.mock(|when, then| {
619+
when.method(GET).path("/hello").header_exists("Authorization");
620+
then.status(200).body("Hello World");
621+
});
622+
623+
// Initialize adapter
624+
let mut adapter = Adapter::new(&AdapterOptions {
625+
host: app_server.host(),
626+
port: app_server.port().to_string(),
627+
readiness_check_port: app_server.port().to_string(),
628+
readiness_check_path: "/healthcheck".to_string(),
629+
authorization_source: Some("auth-token".to_string()),
630+
..Default::default()
631+
});
632+
633+
// // Call the adapter service with basic request
634+
let req = LambdaEventBuilder::new()
635+
.with_path("/hello")
636+
.with_header("auth-token", "Bearer token")
637+
.build();
638+
639+
// We convert to Request object because it allows us to add
640+
// the lambda Context
641+
let mut request = Request::from(req);
642+
add_lambda_context_to_request(&mut request);
643+
644+
let response = adapter.call(request).await.expect("Request failed");
645+
646+
// Assert endpoint was called once
647+
hello.assert();
648+
649+
// and response has expected content
650+
assert_eq!(200, response.status());
651+
assert_eq!(response.headers().get("content-length").unwrap(), "11");
652+
assert_eq!("Hello World", body_to_string(response).await);
653+
}
654+
610655
#[tokio::test]
611656
async fn test_http_context_multi_headers() {
612657
// Start app server

0 commit comments

Comments
 (0)