Skip to content

Commit f94dac8

Browse files
committed
fix(mmds): Set Content-Type header correctly
The MMDS response "Content-Type" header was always "application/json" regardless of the request "Accept" header, although MMDS responds different contents depending on the "Accept" header. Without "Accept" header or with empty "Accept:" or "Accept: */*", micro-http defaults to "Accept: text/plain". In such a case, MMDS behaves like IMDS and the response content is plain text, so "Content-Type" should be "text/plain". Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 1d0f9af commit f94dac8

File tree

2 files changed

+134
-8
lines changed

2 files changed

+134
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ and this project adheres to
3131

3232
- [#5222](https://github.com/firecracker-microvm/firecracker/pull/5222): Fixed
3333
network and rng devices locking up on hosts with non 4K pages.
34+
- [#5226](https://github.com/firecracker-microvm/firecracker/pull/5226): Fixed
35+
MMDS to set `Content-Type` header correctly (i.e. `Content-Type: text/plain`
36+
for IMDS-formatted or error responses and `Content-Type: application/json` for
37+
JSON-formatted responses).
3438

3539
## [1.12.0]
3640

src/vmm/src/mmds/mod.rs

Lines changed: 130 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ impl From<MediaType> for OutputFormat {
5151
}
5252

5353
// Builds the `micro_http::Response` with a given HTTP version, status code, and body.
54-
fn build_response(http_version: Version, status_code: StatusCode, body: Body) -> Response {
54+
fn build_response(
55+
http_version: Version,
56+
status_code: StatusCode,
57+
content_type: MediaType,
58+
body: Body,
59+
) -> Response {
5560
let mut response = Response::new(http_version, status_code);
61+
response.set_content_type(content_type);
5662
response.set_body(body);
5763
response
5864
}
@@ -105,6 +111,7 @@ pub fn convert_to_response(mmds: Arc<Mutex<Mmds>>, request: Request) -> Response
105111
return build_response(
106112
request.http_version(),
107113
StatusCode::BadRequest,
114+
MediaType::PlainText,
108115
Body::new(VmmMmdsError::InvalidURI.to_string()),
109116
);
110117
}
@@ -125,6 +132,7 @@ fn respond_to_request_mmdsv1(mmds: &Mmds, request: Request) -> Response {
125132
let mut response = build_response(
126133
request.http_version(),
127134
StatusCode::MethodNotAllowed,
135+
MediaType::PlainText,
128136
Body::new(VmmMmdsError::MethodNotAllowed.to_string()),
129137
);
130138
response.allow_method(Method::Get);
@@ -141,6 +149,7 @@ fn respond_to_request_mmdsv2(mmds: &mut Mmds, request: Request) -> Response {
141149
return build_response(
142150
request.http_version(),
143151
StatusCode::BadRequest,
152+
MediaType::PlainText,
144153
Body::new(err.to_string()),
145154
);
146155
}
@@ -154,6 +163,7 @@ fn respond_to_request_mmdsv2(mmds: &mut Mmds, request: Request) -> Response {
154163
let mut response = build_response(
155164
request.http_version(),
156165
StatusCode::MethodNotAllowed,
166+
MediaType::PlainText,
157167
Body::new(VmmMmdsError::MethodNotAllowed.to_string()),
158168
);
159169
response.allow_method(Method::Get);
@@ -176,6 +186,7 @@ fn respond_to_get_request_checked(
176186
return build_response(
177187
request.http_version(),
178188
StatusCode::Unauthorized,
189+
MediaType::PlainText,
179190
Body::new(error_msg),
180191
);
181192
}
@@ -187,6 +198,7 @@ fn respond_to_get_request_checked(
187198
Ok(false) => build_response(
188199
request.http_version(),
189200
StatusCode::Unauthorized,
201+
MediaType::PlainText,
190202
Body::new(VmmMmdsError::InvalidToken.to_string()),
191203
),
192204
Err(_) => unreachable!(),
@@ -200,10 +212,13 @@ fn respond_to_get_request_unchecked(mmds: &Mmds, request: Request) -> Response {
200212
// sanitize the URI.
201213
let json_path = sanitize_uri(uri.to_string());
202214

203-
match mmds.get_value(json_path, request.headers.accept().into()) {
215+
let content_type = request.headers.accept();
216+
217+
match mmds.get_value(json_path, content_type.into()) {
204218
Ok(response_body) => build_response(
205219
request.http_version(),
206220
StatusCode::OK,
221+
content_type,
207222
Body::new(response_body),
208223
),
209224
Err(err) => match err {
@@ -212,17 +227,20 @@ fn respond_to_get_request_unchecked(mmds: &Mmds, request: Request) -> Response {
212227
build_response(
213228
request.http_version(),
214229
StatusCode::NotFound,
230+
MediaType::PlainText,
215231
Body::new(error_msg),
216232
)
217233
}
218234
MmdsError::UnsupportedValueType => build_response(
219235
request.http_version(),
220236
StatusCode::NotImplemented,
237+
MediaType::PlainText,
221238
Body::new(err.to_string()),
222239
),
223240
MmdsError::DataStoreLimitExceeded => build_response(
224241
request.http_version(),
225242
StatusCode::PayloadTooLarge,
243+
MediaType::PlainText,
226244
Body::new(err.to_string()),
227245
),
228246
_ => unreachable!(),
@@ -248,6 +266,7 @@ fn respond_to_put_request(
248266
return build_response(
249267
request.http_version(),
250268
StatusCode::BadRequest,
269+
MediaType::PlainText,
251270
Body::new(error_msg),
252271
);
253272
}
@@ -262,6 +281,7 @@ fn respond_to_put_request(
262281
return build_response(
263282
request.http_version(),
264283
StatusCode::NotFound,
284+
MediaType::PlainText,
265285
Body::new(error_msg),
266286
);
267287
}
@@ -273,6 +293,7 @@ fn respond_to_put_request(
273293
return build_response(
274294
request.http_version(),
275295
StatusCode::BadRequest,
296+
MediaType::PlainText,
276297
Body::new(VmmMmdsError::NoTtlProvided.to_string()),
277298
);
278299
}
@@ -281,15 +302,16 @@ fn respond_to_put_request(
281302
// Generate token.
282303
let result = mmds.generate_token(ttl_seconds);
283304
match result {
284-
Ok(token) => {
285-
let mut response =
286-
build_response(request.http_version(), StatusCode::OK, Body::new(token));
287-
response.set_content_type(MediaType::PlainText);
288-
response
289-
}
305+
Ok(token) => build_response(
306+
request.http_version(),
307+
StatusCode::OK,
308+
MediaType::PlainText,
309+
Body::new(token),
310+
),
290311
Err(err) => build_response(
291312
request.http_version(),
292313
StatusCode::BadRequest,
314+
MediaType::PlainText,
293315
Body::new(err.to_string()),
294316
),
295317
}
@@ -343,6 +365,31 @@ mod tests {
343365
}"#
344366
}
345367

368+
fn get_plain_text_data() -> &'static str {
369+
"age\nname/\nphones/"
370+
}
371+
372+
fn generate_request_and_expected_response(
373+
request_bytes: &[u8],
374+
media_type: MediaType,
375+
) -> (Request, Response) {
376+
let request = Request::try_from(request_bytes, None).unwrap();
377+
378+
let mut response = Response::new(Version::Http10, StatusCode::OK);
379+
response.set_content_type(media_type);
380+
let body = match media_type {
381+
MediaType::ApplicationJson => {
382+
let mut body = get_json_data().to_string();
383+
body.retain(|c| !c.is_whitespace());
384+
body
385+
}
386+
MediaType::PlainText => get_plain_text_data().to_string(),
387+
};
388+
response.set_body(Body::new(body));
389+
390+
(request, response)
391+
}
392+
346393
#[test]
347394
fn test_sanitize_uri() {
348395
let sanitized = "/a/b/c/d";
@@ -362,6 +409,66 @@ mod tests {
362409
assert_eq!(sanitize_uri("//aa//bb///cc//d".to_owned()), "/aa/bb/cc/d");
363410
}
364411

412+
#[test]
413+
fn test_request_accept_header() {
414+
// This test validates the response `Content-Type` header and the response content for
415+
// various request `Accept` headers.
416+
417+
// Populate MMDS with data.
418+
let mmds = populate_mmds();
419+
420+
// Test without `Accept` header. micro-http defaults to `Accept: text/plain`.
421+
let (request, expected_response) = generate_request_and_expected_response(
422+
b"GET http://169.254.169.254/ HTTP/1.0\r\n\r\n",
423+
MediaType::PlainText,
424+
);
425+
assert_eq!(
426+
convert_to_response(mmds.clone(), request),
427+
expected_response
428+
);
429+
430+
// Test with empty `Accept` header. micro-http defaults to `Accept: text/plain`.
431+
let (request, expected_response) = generate_request_and_expected_response(
432+
b"GET http://169.254.169.254/ HTTP/1.0\r\n\"
433+
Accept:\r\n\r\n",
434+
MediaType::PlainText,
435+
);
436+
assert_eq!(
437+
convert_to_response(mmds.clone(), request),
438+
expected_response
439+
);
440+
441+
// Test with `Accept: */*` header.
442+
let (request, expected_response) = generate_request_and_expected_response(
443+
b"GET http://169.254.169.254/ HTTP/1.0\r\n\"
444+
Accept: */*\r\n\r\n",
445+
MediaType::PlainText,
446+
);
447+
assert_eq!(
448+
convert_to_response(mmds.clone(), request),
449+
expected_response
450+
);
451+
452+
// Test with `Accept: text/plain`.
453+
let (request, expected_response) = generate_request_and_expected_response(
454+
b"GET http://169.254.169.254/ HTTP/1.0\r\n\
455+
Accept: text/plain\r\n\r\n",
456+
MediaType::PlainText,
457+
);
458+
assert_eq!(
459+
convert_to_response(mmds.clone(), request),
460+
expected_response
461+
);
462+
463+
// Test with `Accept: application/json`.
464+
let (request, expected_response) = generate_request_and_expected_response(
465+
b"GET http://169.254.169.254/ HTTP/1.0\r\n\
466+
Accept: application/json\r\n\r\n",
467+
MediaType::ApplicationJson,
468+
);
469+
assert_eq!(convert_to_response(mmds, request), expected_response);
470+
}
471+
365472
#[test]
366473
fn test_respond_to_request_mmdsv1() {
367474
// Populate MMDS with data.
@@ -381,6 +488,7 @@ mod tests {
381488
let request_bytes = b"GET http://169.254.169.254/invalid HTTP/1.0\r\n\r\n";
382489
let request = Request::try_from(request_bytes, None).unwrap();
383490
let mut expected_response = Response::new(Version::Http10, StatusCode::NotFound);
491+
expected_response.set_content_type(MediaType::PlainText);
384492
expected_response.set_body(Body::new(
385493
VmmMmdsError::ResourceNotFound(String::from("/invalid")).to_string(),
386494
));
@@ -391,6 +499,7 @@ mod tests {
391499
let request_bytes = b"GET /age HTTP/1.1\r\n\r\n";
392500
let request = Request::try_from(request_bytes, None).unwrap();
393501
let mut expected_response = Response::new(Version::Http11, StatusCode::NotImplemented);
502+
expected_response.set_content_type(MediaType::PlainText);
394503
let body = "Cannot retrieve value. The value has an unsupported type.".to_string();
395504
expected_response.set_body(Body::new(body));
396505
let actual_response = convert_to_response(mmds.clone(), request);
@@ -403,6 +512,7 @@ mod tests {
403512
let request = Request::try_from(request_bytes.as_bytes(), None).unwrap();
404513
let mut expected_response =
405514
Response::new(Version::Http10, StatusCode::MethodNotAllowed);
515+
expected_response.set_content_type(MediaType::PlainText);
406516
expected_response.set_body(Body::new(VmmMmdsError::MethodNotAllowed.to_string()));
407517
expected_response.allow_method(Method::Get);
408518
let actual_response = convert_to_response(mmds.clone(), request);
@@ -413,6 +523,7 @@ mod tests {
413523
let request_bytes = b"GET http:// HTTP/1.0\r\n\r\n";
414524
let request = Request::try_from(request_bytes, None).unwrap();
415525
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
526+
expected_response.set_content_type(MediaType::PlainText);
416527
expected_response.set_body(Body::new(VmmMmdsError::InvalidURI.to_string()));
417528
let actual_response = convert_to_response(mmds.clone(), request);
418529
assert_eq!(actual_response, expected_response);
@@ -458,6 +569,7 @@ mod tests {
458569
let request_bytes = b"PATCH http://169.254.169.255/ HTTP/1.0\r\n\r\n";
459570
let request = Request::try_from(request_bytes, None).unwrap();
460571
let mut expected_response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
572+
expected_response.set_content_type(MediaType::PlainText);
461573
expected_response.set_body(Body::new(VmmMmdsError::MethodNotAllowed.to_string()));
462574
expected_response.allow_method(Method::Get);
463575
expected_response.allow_method(Method::Put);
@@ -470,6 +582,7 @@ mod tests {
470582
X-metadata-token-ttl-seconds: application/json\r\n\r\n";
471583
let request = Request::try_from(request_bytes, None).unwrap();
472584
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
585+
expected_response.set_content_type(MediaType::PlainText);
473586
expected_response.set_body(Body::new(
474587
"Invalid header. Reason: Invalid value. Key:X-metadata-token-ttl-seconds; \
475588
Value:application/json"
@@ -484,6 +597,7 @@ mod tests {
484597
X-Forwarded-For: 203.0.113.195\r\n\r\n";
485598
let request = Request::try_from(request_bytes, None).unwrap();
486599
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
600+
expected_response.set_content_type(MediaType::PlainText);
487601
expected_response.set_body(Body::new(
488602
"Invalid header. Reason: Unsupported header name. Key: X-Forwarded-For".to_string(),
489603
));
@@ -495,6 +609,7 @@ mod tests {
495609
X-metadata-token-ttl-seconds: 60\r\n\r\n";
496610
let request = Request::try_from(request_bytes, None).unwrap();
497611
let mut expected_response = Response::new(Version::Http10, StatusCode::NotFound);
612+
expected_response.set_content_type(MediaType::PlainText);
498613
expected_response.set_body(Body::new(
499614
VmmMmdsError::ResourceNotFound(String::from("/token")).to_string(),
500615
));
@@ -511,6 +626,7 @@ mod tests {
511626
);
512627
let request = Request::try_from(request_bytes.as_bytes(), None).unwrap();
513628
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
629+
expected_response.set_content_type(MediaType::PlainText);
514630
let error_msg = format!(
515631
"Invalid time to live value provided for token: {}. Please provide a value \
516632
between {} and {}.",
@@ -525,6 +641,7 @@ mod tests {
525641
let request_bytes = b"PUT http://169.254.169.254/latest/api/token HTTP/1.0\r\n\r\n";
526642
let request = Request::try_from(request_bytes, None).unwrap();
527643
let mut expected_response = Response::new(Version::Http10, StatusCode::BadRequest);
644+
expected_response.set_content_type(MediaType::PlainText);
528645
expected_response.set_body(Body::new(VmmMmdsError::NoTtlProvided.to_string()));
529646
let actual_response = convert_to_response(mmds.clone(), request);
530647
assert_eq!(actual_response, expected_response);
@@ -559,6 +676,7 @@ mod tests {
559676
);
560677
let request = Request::try_from(request_bytes.as_bytes(), None).unwrap();
561678
let mut expected_response = Response::new(Version::Http11, StatusCode::NotImplemented);
679+
expected_response.set_content_type(MediaType::PlainText);
562680
let body = "Cannot retrieve value. The value has an unsupported type.".to_string();
563681
expected_response.set_body(Body::new(body));
564682
let actual_response = convert_to_response(mmds.clone(), request);
@@ -571,6 +689,7 @@ mod tests {
571689
);
572690
let request = Request::try_from(request_bytes.as_bytes(), None).unwrap();
573691
let mut expected_response = Response::new(Version::Http10, StatusCode::NotFound);
692+
expected_response.set_content_type(MediaType::PlainText);
574693
expected_response.set_body(Body::new(
575694
VmmMmdsError::ResourceNotFound(String::from("/invalid")).to_string(),
576695
));
@@ -581,6 +700,7 @@ mod tests {
581700
let request_bytes = b"GET http://169.254.169.254/ HTTP/1.0\r\n\r\n";
582701
let request = Request::try_from(request_bytes, None).unwrap();
583702
let mut expected_response = Response::new(Version::Http10, StatusCode::Unauthorized);
703+
expected_response.set_content_type(MediaType::PlainText);
584704
expected_response.set_body(Body::new(VmmMmdsError::NoTokenProvided.to_string()));
585705
let actual_response = convert_to_response(mmds.clone(), request);
586706
assert_eq!(actual_response, expected_response);
@@ -590,6 +710,7 @@ mod tests {
590710
X-metadata-token: foo\r\n\r\n";
591711
let request = Request::try_from(request_bytes, None).unwrap();
592712
let mut expected_response = Response::new(Version::Http10, StatusCode::Unauthorized);
713+
expected_response.set_content_type(MediaType::PlainText);
593714
expected_response.set_body(Body::new(VmmMmdsError::InvalidToken.to_string()));
594715
let actual_response = convert_to_response(mmds.clone(), request);
595716
assert_eq!(actual_response, expected_response);
@@ -614,6 +735,7 @@ mod tests {
614735
);
615736
let request = Request::try_from(request_bytes.as_bytes(), None).unwrap();
616737
let mut expected_response = Response::new(Version::Http10, StatusCode::Unauthorized);
738+
expected_response.set_content_type(MediaType::PlainText);
617739
expected_response.set_body(Body::new(VmmMmdsError::InvalidToken.to_string()));
618740
let actual_response = convert_to_response(mmds.clone(), request);
619741
assert_eq!(actual_response, expected_response);

0 commit comments

Comments
 (0)