Skip to content

Commit 0e0161d

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 7e9aa8c commit 0e0161d

File tree

2 files changed

+89
-56
lines changed

2 files changed

+89
-56
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: 85 additions & 56 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,36 @@ 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
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+
}
123+
124+
// The first one should be used when multiple valid headers are included.
99125
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()),
126+
(X_METADATA_TOKEN_HEADER.into(), "first".into()),
127+
(X_AWS_EC2_METADATA_TOKEN_HEADER.into(), "second".into()),
103128
]);
104129
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);
130+
assert_eq!(&x_metadata_token.0.unwrap(), "first");
112131
}
113132

114133
#[test]
@@ -128,48 +147,58 @@ mod tests {
128147
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
129148
assert!(x_metadata_token_ttl_seconds.0.is_none());
130149

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
150+
for header in [
151+
X_METADATA_TOKEN_TTL_SECONDS_HEADER,
152+
X_AWS_EC2_METADATA_TOKEN_SSL_SECONDS_HEADER,
153+
] {
154+
let seconds = 60;
155+
156+
// Valid header
157+
let custom_headers = HashMap::from([(header.into(), seconds.to_string())]);
158+
let x_metadata_token_ttl_seconds =
159+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
160+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
161+
162+
// Valid header in unrelated custom headers
163+
let custom_headers = HashMap::from([
164+
("Some-Header".into(), "10".into()),
165+
("Another-Header".into(), "value".into()),
166+
(header.into(), seconds.to_string()),
167+
]);
168+
let x_metadata_token_ttl_seconds =
169+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
170+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
171+
172+
// Test case-insensitiveness
173+
let custom_headers = HashMap::from([(to_mixed_case(header), seconds.to_string())]);
174+
let x_metadata_token_ttl_seconds =
175+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap();
176+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), seconds);
177+
178+
// Invalid value
179+
let mixed_case_header = to_mixed_case(header);
180+
let invalid_seconds = "-60";
181+
let custom_headers =
182+
HashMap::from([(mixed_case_header.clone(), invalid_seconds.to_string())]);
183+
assert_eq!(
184+
XMetadataTokenTtlSeconds::try_from(&custom_headers).unwrap_err(),
185+
RequestError::HeaderError(HttpHeaderError::InvalidValue(
186+
mixed_case_header,
187+
invalid_seconds.to_string()
188+
))
189+
);
190+
}
191+
192+
// The first one should be used when multiple valid headers are included.
142193
let custom_headers = HashMap::from([
143-
("Some-Header".into(), "10".into()),
144-
("Another-Header".into(), "value".into()),
194+
(X_METADATA_TOKEN_TTL_SECONDS_HEADER.into(), 1.to_string()),
145195
(
146-
X_METADATA_TOKEN_TTL_SECONDS_HEADER.into(),
147-
seconds.to_string(),
196+
X_AWS_EC2_METADATA_TOKEN_SSL_SECONDS_HEADER.into(),
197+
2.to_string(),
148198
),
149199
]);
150200
let x_metadata_token_ttl_seconds =
151201
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-
);
202+
assert_eq!(x_metadata_token_ttl_seconds.0.unwrap(), 1);
174203
}
175204
}

0 commit comments

Comments
 (0)