Skip to content

Commit 0ba2b7f

Browse files
committed
feat(mmds): Accept EC2 IMDS-compatible custom headers
The custom headers supported by MMDS (X-metadata-token and X-metadata-token-ttl-seconds) are different from what EC2 IMDS supports (X-aws-ec2-metadata-token and X-aws-ec2-metadata-token-ttl-seconds). Supporting EC2 IMDS-compatible custom headers, users are able to make their workloads work with MMDS out of the box. Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 6eb994f commit 0ba2b7f

File tree

3 files changed

+81
-69
lines changed

3 files changed

+81
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ and this project adheres to
1616
- [#5175](https://github.com/firecracker-microvm/firecracker/pull/5175): Allow
1717
including a custom cpu template directly in the json configuration file passed
1818
to `--config-file` under the `cpu_config` key.
19+
- [#XXXX](https://github.com/firecracker-microvm/firecracker/pull/XXXX):
20+
Extended MMDS to support the EC2 IMDS-compatible session token headers (i.e.
21+
"X-aws-ec2-metadata-token" and "X-aws-ec2-metadata-token-ttl-seconds")
22+
alongside the MMDS-specific ones.
1923

2024
### Changed
2125

src/vmm/src/mmds/token_headers.rs

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ pub struct XMetadataToken(pub Option<String>);
1616

1717
// Defined in lowercase since HTTP headers are case-insensitive.
1818
const X_METADATA_TOKEN_HEADER: &str = "x-metadata-token";
19+
const X_AWS_EC2_METADATA_TOKEN_HEADER: &str = "x-aws-ec2-metadata-token";
1920

2021
impl From<&HashMap<String, String>> for XMetadataToken {
2122
fn from(custom_headers: &HashMap<String, String>) -> Self {
2223
Self(
2324
custom_headers
2425
.iter()
25-
.find(|(k, _)| k.to_lowercase() == X_METADATA_TOKEN_HEADER)
26+
.find(|(k, _)| {
27+
let k = k.to_lowercase();
28+
k == X_METADATA_TOKEN_HEADER || k == X_AWS_EC2_METADATA_TOKEN_HEADER
29+
})
2630
.map(|(_, v)| v.to_string()),
2731
)
2832
}
@@ -35,14 +39,19 @@ pub struct XMetadataTokenTtlSeconds(pub Option<u32>);
3539

3640
// Defined in lowercase since HTTP headers are case-insensitive.
3741
const X_METADATA_TOKEN_TTL_SECONDS_HEADER: &str = "x-metadata-token-ttl-seconds";
42+
const X_AWS_EC2_METADATA_TOKEN_SSL_SECONDS_HEADER: &str = "x-aws-ec2-metadata-token-ttl-seconds";
3843

3944
impl TryFrom<&HashMap<String, String>> for XMetadataTokenTtlSeconds {
4045
type Error = RequestError;
4146

4247
fn try_from(custom_headers: &HashMap<String, String>) -> Result<Self, RequestError> {
4348
let seconds = custom_headers
4449
.iter()
45-
.find(|(k, _)| k.to_lowercase() == X_METADATA_TOKEN_TTL_SECONDS_HEADER)
50+
.find(|(k, _)| {
51+
let k = k.to_lowercase();
52+
k == X_METADATA_TOKEN_TTL_SECONDS_HEADER
53+
|| k == X_AWS_EC2_METADATA_TOKEN_SSL_SECONDS_HEADER
54+
})
4655
.map(|(k, v)| {
4756
v.parse::<u32>().map_err(|_| {
4857
RequestError::HeaderError(HttpHeaderError::InvalidValue(
@@ -89,26 +98,28 @@ mod tests {
8998
let x_metadata_token = XMetadataToken::from(&custom_headers);
9099
assert!(x_metadata_token.0.is_none());
91100

92-
// Valid header
93-
let token = "THIS_IS_TOKEN";
94-
let custom_headers = HashMap::from([(X_METADATA_TOKEN_HEADER.into(), token.into())]);
95-
let x_metadata_token = XMetadataToken::from(&custom_headers);
96-
assert_eq!(&x_metadata_token.0.unwrap(), token);
97-
98-
// Valid header in unrelated custom headers
99-
let custom_headers = HashMap::from([
100-
("Some-Header".into(), "10".into()),
101-
("Another-Header".into(), "value".into()),
102-
(X_METADATA_TOKEN_HEADER.into(), token.into()),
103-
]);
104-
let x_metadata_token = XMetadataToken::from(&custom_headers);
105-
assert_eq!(&x_metadata_token.0.unwrap(), token);
106-
107-
// Test case-insensitiveness
108-
let custom_headers =
109-
HashMap::from([(to_mixed_case(X_METADATA_TOKEN_HEADER), token.into())]);
110-
let x_metadata_token = XMetadataToken::from(&custom_headers);
111-
assert_eq!(&x_metadata_token.0.unwrap(), token);
101+
for header in [X_METADATA_TOKEN_HEADER, X_AWS_EC2_METADATA_TOKEN_HEADER] {
102+
let token = "THIS_IS_TOKEN";
103+
104+
// Valid header
105+
let custom_headers = HashMap::from([(header.into(), token.into())]);
106+
let x_metadata_token = XMetadataToken::from(&custom_headers);
107+
assert_eq!(&x_metadata_token.0.unwrap(), token);
108+
109+
// Valid header in unrelated custom headers
110+
let custom_headers = HashMap::from([
111+
("Some-Header".into(), "10".into()),
112+
("Another-Header".into(), "value".into()),
113+
(header.into(), token.into()),
114+
]);
115+
let x_metadata_token = XMetadataToken::from(&custom_headers);
116+
assert_eq!(&x_metadata_token.0.unwrap(), token);
117+
118+
// Test case-insensitiveness
119+
let custom_headers = HashMap::from([(to_mixed_case(header), token.into())]);
120+
let x_metadata_token = XMetadataToken::from(&custom_headers);
121+
assert_eq!(&x_metadata_token.0.unwrap(), token);
122+
}
112123
}
113124

114125
#[test]
@@ -128,48 +139,46 @@ mod tests {
128139
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
129140
assert!(x_metadata_token_ttl_seconds.0.is_none());
130141

131-
// Valid header
132-
let seconds = 60;
133-
let custom_headers = HashMap::from([(
134-
X_METADATA_TOKEN_TTL_SECONDS_HEADER.into(),
135-
seconds.to_string(),
136-
)]);
137-
let x_metadata_token_ttl_seconds =
138-
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
139-
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
140-
141-
// Valid header in unrelated custom headers
142-
let custom_headers = HashMap::from([
143-
("Some-Header".into(), "10".into()),
144-
("Another-Header".into(), "value".into()),
145-
(
146-
X_METADATA_TOKEN_TTL_SECONDS_HEADER.into(),
147-
seconds.to_string(),
148-
),
149-
]);
150-
let x_metadata_token_ttl_seconds =
151-
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
152-
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
153-
154-
// Test case-insensitiveness
155-
let custom_headers = HashMap::from([(
156-
to_mixed_case(X_METADATA_TOKEN_TTL_SECONDS_HEADER),
157-
seconds.to_string(),
158-
)]);
159-
let x_metadata_token_ttl_seconds =
160-
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
161-
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
162-
163-
// Invalid value
164-
let header_name = "X-metadata-token-ttl-seconds";
165-
let invalid_seconds = "-60";
166-
let custom_headers = HashMap::from([(header_name.into(), invalid_seconds.to_string())]);
167-
assert_eq!(
168-
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap_err(),
169-
RequestError::HeaderError(HttpHeaderError::InvalidValue(
170-
header_name.into(),
171-
invalid_seconds.to_string()
172-
))
173-
);
142+
for header in [
143+
X_METADATA_TOKEN_TTL_SECONDS_HEADER,
144+
X_AWS_EC2_METADATA_TOKEN_SSL_SECONDS_HEADER,
145+
] {
146+
let seconds = 60;
147+
148+
// Valid header
149+
let custom_headers = HashMap::from([(header.into(), seconds.to_string())]);
150+
let x_metadata_token_ttl_seconds =
151+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
152+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
153+
154+
// Valid header in unrelated custom headers
155+
let custom_headers = HashMap::from([
156+
("Some-Header".into(), "10".into()),
157+
("Another-Header".into(), "value".into()),
158+
(header.into(), seconds.to_string()),
159+
]);
160+
let x_metadata_token_ttl_seconds =
161+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
162+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
163+
164+
// Test case-insensitiveness
165+
let custom_headers = HashMap::from([(to_mixed_case(header), seconds.to_string())]);
166+
let x_metadata_token_ttl_seconds =
167+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
168+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
169+
170+
// Invalid value
171+
let mixed_case_header = to_mixed_case(header);
172+
let invalid_seconds = "-60";
173+
let custom_headers =
174+
HashMap::from([(mixed_case_header.clone(), invalid_seconds.to_string())]);
175+
assert_eq!(
176+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap_err(),
177+
RequestError::HeaderError(HttpHeaderError::InvalidValue(
178+
mixed_case_header,
179+
invalid_seconds.to_string()
180+
))
181+
);
182+
}
174183
}
175184
}

tests/integration_tests/functional/test_mmds.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ def test_mmds_v2_negative(uvm_plain):
647647
# Check `GET` request fails when token is not provided.
648648
cmd = generate_mmds_get_request(DEFAULT_IPV4)
649649
expected = (
650-
"No MMDS token provided. Use `X-metadata-token` header "
650+
"No MMDS token provided. Use `X-metadata-token` or `X-aws-ec2-metadata-token` header "
651651
"to specify the session token."
652652
)
653653
run_guest_cmd(ssh_connection, cmd, expected)
@@ -664,9 +664,8 @@ def test_mmds_v2_negative(uvm_plain):
664664
# Check `PUT` request fails when token TTL is not provided.
665665
cmd = f"curl -m 2 -s -X PUT http://{DEFAULT_IPV4}/latest/api/token"
666666
expected = (
667-
"Token time to live value not found. Use "
668-
"`X-metadata-token-ttl-seconds` header to specify "
669-
"the token's lifetime."
667+
"Token time to live value not found. Use `X-metadata-token-ttl-seconds` or "
668+
"`X-aws-ec2-metadata-token-ttl-seconds` header to specify the token's lifetime."
670669
)
671670
run_guest_cmd(ssh_connection, cmd, expected)
672671

0 commit comments

Comments
 (0)