Skip to content

Commit 6eb994f

Browse files
committed
fix(mmds): Validate X-metadata-token-ttl-seconds header only if needed
We checked that "X-metadata-token-ttl-seconds" has a valid positive number regardless of the request type. On the oher hand, EC2 IMDS only validates it only if it is a PUT request to "/latest/api/token". Such a behavioral difference from EC2 IMDS could result in unexpected issues. To line up with EC2 IMDS, breaks `TokenHeaders` down to dedicated structs (`XMetadataToken` and `XMetadataTokenTtlSeconds`) and parses "X-metadata-token" only for GET requests and "X-metadata-token-ttl-seconds" only for PUT requests to "latest/api/token". Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 91c7798 commit 6eb994f

File tree

3 files changed

+203
-185
lines changed

3 files changed

+203
-185
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ and this project adheres to
2424
Incremental snapshots remain in developer preview.
2525
- [#5282](https://github.com/firecracker-microvm/firecracker/pull/5282): Updated
2626
jailer to no longer require the executable file name to contain `firecracker`.
27+
- [#XXXX](https://github.com/firecracker-microvm/firecracker/pull/XXXX): Changed
28+
MMDS to validate the value of "X-metadata-token-ttl-seconds" header only if it
29+
is a PUT request to /latest/api/token, as in EC2 IMDS.
2730

2831
### Deprecated
2932

src/vmm/src/mmds/mod.rs

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use micro_http::{
1717
Body, HttpHeaderError, MediaType, Method, Request, RequestError, Response, StatusCode, Version,
1818
};
1919
use serde_json::{Map, Value};
20-
use token_headers::TokenHeaders;
20+
use token_headers::{XMetadataToken, XMetadataTokenTtlSeconds};
2121

2222
use crate::mmds::data_store::{Mmds, MmdsDatastoreError as MmdsError, MmdsVersion, OutputFormat};
2323
use crate::mmds::token::PATH_TO_TOKEN;
@@ -142,23 +142,10 @@ fn respond_to_request_mmdsv1(mmds: &Mmds, request: Request) -> Response {
142142
}
143143

144144
fn respond_to_request_mmdsv2(mmds: &mut Mmds, request: Request) -> Response {
145-
// Fetch custom headers from request.
146-
let token_headers = match TokenHeaders::try_from(request.headers.custom_entries()) {
147-
Ok(token_headers) => token_headers,
148-
Err(err) => {
149-
return build_response(
150-
request.http_version(),
151-
StatusCode::BadRequest,
152-
MediaType::PlainText,
153-
Body::new(err.to_string()),
154-
);
155-
}
156-
};
157-
158145
// Allow only GET and PUT requests.
159146
match request.method() {
160-
Method::Get => respond_to_get_request_checked(mmds, request, token_headers),
161-
Method::Put => respond_to_put_request(mmds, request, token_headers),
147+
Method::Get => respond_to_get_request_checked(mmds, request),
148+
Method::Put => respond_to_put_request(mmds, request),
162149
_ => {
163150
let mut response = build_response(
164151
request.http_version(),
@@ -173,13 +160,10 @@ fn respond_to_request_mmdsv2(mmds: &mut Mmds, request: Request) -> Response {
173160
}
174161
}
175162

176-
fn respond_to_get_request_checked(
177-
mmds: &Mmds,
178-
request: Request,
179-
token_headers: TokenHeaders,
180-
) -> Response {
181-
// Get MMDS token from custom headers.
182-
let token = match token_headers.x_metadata_token() {
163+
fn respond_to_get_request_checked(mmds: &Mmds, request: Request) -> Response {
164+
// Check whether a token exists.
165+
let x_metadata_token = XMetadataToken::from(request.headers.custom_entries());
166+
let token = match x_metadata_token.0 {
183167
Some(token) => token,
184168
None => {
185169
let error_msg = VmmMmdsError::NoTokenProvided.to_string();
@@ -192,8 +176,8 @@ fn respond_to_get_request_checked(
192176
}
193177
};
194178

195-
// Validate MMDS token.
196-
match mmds.is_valid_token(token) {
179+
// Validate the token.
180+
match mmds.is_valid_token(&token) {
197181
Ok(true) => respond_to_get_request_unchecked(mmds, request),
198182
Ok(false) => build_response(
199183
request.http_version(),
@@ -248,17 +232,11 @@ fn respond_to_get_request_unchecked(mmds: &Mmds, request: Request) -> Response {
248232
}
249233
}
250234

251-
fn respond_to_put_request(
252-
mmds: &mut Mmds,
253-
request: Request,
254-
token_headers: TokenHeaders,
255-
) -> Response {
235+
fn respond_to_put_request(mmds: &mut Mmds, request: Request) -> Response {
236+
let custom_headers = request.headers.custom_entries();
237+
256238
// Reject `PUT` requests that contain `X-Forwarded-For` header.
257-
if request
258-
.headers
259-
.custom_entries()
260-
.contains_key(REJECTED_HEADER)
261-
{
239+
if custom_headers.contains_key(REJECTED_HEADER) {
262240
let error_msg = RequestError::HeaderError(HttpHeaderError::UnsupportedName(
263241
REJECTED_HEADER.to_string(),
264242
))
@@ -287,16 +265,26 @@ fn respond_to_put_request(
287265
}
288266

289267
// Get token lifetime value.
290-
let ttl_seconds = match token_headers.x_metadata_token_ttl_seconds() {
291-
Some(ttl_seconds) => ttl_seconds,
292-
None => {
268+
let ttl_seconds = match XMetadataTokenTtlSeconds::try_from(custom_headers) {
269+
Err(err) => {
293270
return build_response(
294271
request.http_version(),
295272
StatusCode::BadRequest,
296273
MediaType::PlainText,
297-
Body::new(VmmMmdsError::NoTtlProvided.to_string()),
274+
Body::new(err.to_string()),
298275
);
299276
}
277+
Ok(ttl_seconds) => match ttl_seconds.0 {
278+
Some(ttl_seconds) => ttl_seconds,
279+
None => {
280+
return build_response(
281+
request.http_version(),
282+
StatusCode::BadRequest,
283+
MediaType::PlainText,
284+
Body::new(VmmMmdsError::NoTtlProvided.to_string()),
285+
);
286+
}
287+
},
300288
};
301289

302290
// Generate token.
@@ -530,7 +518,7 @@ mod tests {
530518
let actual_response = convert_to_response(mmds.clone(), request);
531519
assert_eq!(actual_response, expected_response);
532520

533-
// Test invalid custom header value is ignored when V1 is configured.
521+
// Test invalid custom header value is ignored if not PUT request to /latest/api/token.
534522
let (request, expected_response) = generate_request_and_expected_response(
535523
b"GET http://169.254.169.254/ HTTP/1.0\r\n\
536524
Accept: application/json\r\n\
@@ -576,23 +564,6 @@ mod tests {
576564
let actual_response = convert_to_response(mmds.clone(), request);
577565
assert_eq!(actual_response, expected_response);
578566

579-
// Test invalid value for custom header.
580-
let request = Request::try_from(
581-
b"GET http://169.254.169.254/ HTTP/1.0\r\n\
582-
Accept: application/json\r\n\
583-
X-metadata-token-ttl-seconds: -60\r\n\r\n",
584-
None,
585-
)
586-
.unwrap();
587-
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
588-
expected_response.set_content_type(MediaType::PlainText);
589-
expected_response.set_body(Body::new(
590-
"Invalid header. Reason: Invalid value. Key:X-metadata-token-ttl-seconds; Value:-60"
591-
.to_string(),
592-
));
593-
let actual_response = convert_to_response(mmds.clone(), request);
594-
assert_eq!(actual_response, expected_response);
595-
596567
// Test PUT requests.
597568
// Unsupported `X-Forwarded-For` header present.
598569
let request = Request::try_from(
@@ -624,6 +595,22 @@ mod tests {
624595
let actual_response = convert_to_response(mmds.clone(), request);
625596
assert_eq!(actual_response, expected_response);
626597

598+
// Test invalid X-metadata-token-ttl-seconds value gets BadRequest.
599+
let request = Request::try_from(
600+
b"PUT http://169.254.169.254/latest/api/token HTTP/1.0\r\n\
601+
X-metadata-token-ttl-seconds: -60\r\n\r\n",
602+
None,
603+
)
604+
.unwrap();
605+
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
606+
expected_response.set_content_type(MediaType::PlainText);
607+
expected_response.set_body(Body::new(
608+
"Invalid header. Reason: Invalid value. Key:X-metadata-token-ttl-seconds; Value:-60"
609+
.to_string(),
610+
));
611+
let actual_response = convert_to_response(mmds.clone(), request);
612+
assert_eq!(actual_response, expected_response);
613+
627614
// Test invalid lifetime values for token.
628615
let invalid_values = [MIN_TOKEN_TTL_SECONDS - 1, MAX_TOKEN_TTL_SECONDS + 1];
629616
for invalid_value in invalid_values.iter() {
@@ -687,6 +674,21 @@ mod tests {
687674
let actual_response = convert_to_response(mmds.clone(), request);
688675
assert_eq!(actual_response, expected_response);
689676

677+
// Test invalid customer header value is ignored if not PUT request to /latest/api/token.
678+
#[rustfmt::skip]
679+
let (request, expected_response) = generate_request_and_expected_response(
680+
format!(
681+
"GET http://169.254.169.254/ HTTP/1.0\r\n\
682+
Accept: application/json\r\n\
683+
X-metadata-token: {valid_token}\r\n\
684+
X-metadata-token-ttl-seconds: -60\r\n\r\n",
685+
)
686+
.as_bytes(),
687+
MediaType::ApplicationJson,
688+
);
689+
let actual_response = convert_to_response(mmds.clone(), request);
690+
assert_eq!(actual_response, expected_response);
691+
690692
// Test GET request towards unsupported value type.
691693
#[rustfmt::skip]
692694
let request = Request::try_from(

0 commit comments

Comments
 (0)