Skip to content

Commit 0036de6

Browse files
committed
feat(mmds): Add metric to count GET requests with invalid tokens
In the previous commit, MMDS v1 was made to support token generation but a given token to GET request was not validated. Validates the token and increments a new metric `rx_invalid_token` if it is not valid. Signed-off-by: Takahiro Itazuri <[email protected]>
1 parent 0a1cf52 commit 0036de6

File tree

6 files changed

+58
-7
lines changed

6 files changed

+58
-7
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ and this project adheres to
2424
Extended MMDS to support the EC2 IMDS-compatible session token headers (i.e.
2525
"X-aws-ec2-metadata-token" and "X-aws-ec2-metadata-token-ttl-seconds")
2626
alongside the MMDS-specific ones.
27+
- [#5290](https://github.com/firecracker-microvm/firecracker/pull/5290): Added
28+
`mmds.rx_invalid_token` metric to track the number of GET requests that were
29+
rejected due to token validation failures in MMDS version 2. This metric also
30+
counts requests that would be rejected in MMDS version 2 when MMDS version 1
31+
is configured. This helps users assess readiness for migrating to MMDS version
32+
2.
2733

2834
### Changed
2935

docs/mmds/mmds-user-guide.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ As in version 2, version 1 also supports a session oriented method in order to
235235
make the migration easier. See [the next section](#version-2) for the session
236236
oriented method. Note that version 1 returns a successful response to a `GET`
237237
request even with an invalid token or no token not to break existing workloads.
238+
`mmds.rx_invalid_token` metric tracks the number of `GET` requests with invalid
239+
tokens, helping users evaluate their readiness for migrating to MMDS version 2.
238240

239241
Requests containing any other HTTP methods than `GET` and `PUT` will receive
240242
**405 Method Not Allowed** error.

src/vmm/src/logger/metrics.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,8 @@ pub struct MmdsMetrics {
539539
pub rx_accepted_unusual: SharedIncMetric,
540540
/// The number of buffers which couldn't be parsed as valid Ethernet frames by the MMDS.
541541
pub rx_bad_eth: SharedIncMetric,
542+
/// The number of GET requests with invalid tokens.
543+
pub rx_invalid_token: SharedIncMetric,
542544
/// The total number of successful receive operations by the MMDS.
543545
pub rx_count: SharedIncMetric,
544546
/// The total number of bytes sent by the MMDS.
@@ -562,6 +564,7 @@ impl MmdsMetrics {
562564
rx_accepted_err: SharedIncMetric::new(),
563565
rx_accepted_unusual: SharedIncMetric::new(),
564566
rx_bad_eth: SharedIncMetric::new(),
567+
rx_invalid_token: SharedIncMetric::new(),
565568
rx_count: SharedIncMetric::new(),
566569
tx_bytes: SharedIncMetric::new(),
567570
tx_count: SharedIncMetric::new(),

src/vmm/src/mmds/mod.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use micro_http::{
1818
};
1919
use serde_json::{Map, Value};
2020

21+
use crate::logger::{IncMetric, METRICS};
2122
use crate::mmds::data_store::{Mmds, MmdsDatastoreError as MmdsError, MmdsVersion, OutputFormat};
2223
use crate::mmds::token::PATH_TO_TOKEN;
2324
use crate::mmds::token_headers::{
@@ -143,7 +144,20 @@ pub fn convert_to_response(mmds: Arc<Mutex<Mmds>>, request: Request) -> Response
143144
}
144145

145146
fn respond_to_get_request_v1(mmds: &Mmds, request: Request) -> Response {
146-
// TODO: Increments metrics that will be added in an upcoming commit.
147+
match get_header_value_pair(
148+
request.headers.custom_entries(),
149+
&[X_METADATA_TOKEN_HEADER, X_AWS_EC2_METADATA_TOKEN_HEADER],
150+
) {
151+
Some((_, token)) => {
152+
if !mmds.is_valid_token(token) {
153+
METRICS.mmds.rx_invalid_token.inc();
154+
}
155+
}
156+
None => {
157+
// TODO: Increment a metric that will be added in an upcoming commit.
158+
}
159+
}
160+
147161
respond_to_get_request(mmds, request)
148162
}
149163

@@ -168,12 +182,15 @@ fn respond_to_get_request_v2(mmds: &Mmds, request: Request) -> Response {
168182
// Validate the token.
169183
match mmds.is_valid_token(token) {
170184
true => respond_to_get_request(mmds, request),
171-
false => build_response(
172-
request.http_version(),
173-
StatusCode::Unauthorized,
174-
MediaType::PlainText,
175-
Body::new(VmmMmdsError::InvalidToken.to_string()),
176-
),
185+
false => {
186+
METRICS.mmds.rx_invalid_token.inc();
187+
build_response(
188+
request.http_version(),
189+
StatusCode::Unauthorized,
190+
MediaType::PlainText,
191+
Body::new(VmmMmdsError::InvalidToken.to_string()),
192+
)
193+
}
177194
}
178195
}
179196

@@ -536,8 +553,10 @@ mod tests {
536553
Accept: application/json\r\n\r\n",
537554
MediaType::ApplicationJson,
538555
);
556+
let prev_rx_invalid_token = METRICS.mmds.rx_invalid_token.count();
539557
let actual_response = convert_to_response(mmds.clone(), request);
540558
assert_eq!(actual_response, expected_response);
559+
assert_eq!(prev_rx_invalid_token, METRICS.mmds.rx_invalid_token.count());
541560

542561
// Test valid v2 request.
543562
let request = Request::try_from(
@@ -561,8 +580,10 @@ mod tests {
561580
.as_bytes(),
562581
MediaType::ApplicationJson,
563582
);
583+
let prev_rx_invalid_token = METRICS.mmds.rx_invalid_token.count();
564584
let actual_response = convert_to_response(mmds.clone(), request);
565585
assert_eq!(actual_response, expected_response);
586+
assert_eq!(prev_rx_invalid_token, METRICS.mmds.rx_invalid_token.count());
566587

567588
// Test GET request with invalid token is accepted when v1 is configured.
568589
let (request, expected_response) = generate_request_and_expected_response(
@@ -571,8 +592,13 @@ mod tests {
571592
X-metadata-token: INVALID_TOKEN\r\n\r\n",
572593
MediaType::ApplicationJson,
573594
);
595+
let prev_rx_invalid_token = METRICS.mmds.rx_invalid_token.count();
574596
let actual_response = convert_to_response(mmds, request);
575597
assert_eq!(actual_response, expected_response);
598+
assert_eq!(
599+
prev_rx_invalid_token + 1,
600+
METRICS.mmds.rx_invalid_token.count()
601+
);
576602
}
577603

578604
#[test]
@@ -707,8 +733,10 @@ mod tests {
707733
.as_bytes(),
708734
MediaType::ApplicationJson,
709735
);
736+
let prev_rx_invalid_token = METRICS.mmds.rx_invalid_token.count();
710737
let actual_response = convert_to_response(mmds.clone(), request);
711738
assert_eq!(actual_response, expected_response);
739+
assert_eq!(prev_rx_invalid_token, METRICS.mmds.rx_invalid_token.count());
712740

713741
// Test invalid customer header value is ignored if not PUT request to /latest/api/token.
714742
#[rustfmt::skip]
@@ -801,8 +829,13 @@ mod tests {
801829
let mut expected_response = Response::new(Version::Http10, StatusCode::Unauthorized);
802830
expected_response.set_content_type(MediaType::PlainText);
803831
expected_response.set_body(Body::new(VmmMmdsError::InvalidToken.to_string()));
832+
let prev_rx_invalid_token = METRICS.mmds.rx_invalid_token.count();
804833
let actual_response = convert_to_response(mmds.clone(), request);
805834
assert_eq!(actual_response, expected_response);
835+
assert_eq!(
836+
prev_rx_invalid_token + 1,
837+
METRICS.mmds.rx_invalid_token.count()
838+
);
806839

807840
// Wait for the second token to expire.
808841
std::thread::sleep(Duration::from_secs(1));

tests/host_tools/fcmetrics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def validate_fc_metrics(metrics):
185185
"rx_accepted_err",
186186
"rx_accepted_unusual",
187187
"rx_bad_eth",
188+
"rx_invalid_token",
188189
"rx_count",
189190
"tx_bytes",
190191
"tx_count",

tests/integration_tests/functional/test_mmds.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ def test_mmds_token(uvm_plain, version, imds_compat):
156156
" header to specify the session token."
157157
),
158158
)
159+
metrics = test_microvm.flush_metrics()
160+
assert metrics["mmds"]["rx_invalid_token"] == 0
159161

160162
# GET request with invalid token
161163
cmd = (
@@ -168,11 +170,15 @@ def test_mmds_token(uvm_plain, version, imds_compat):
168170
elif version == "V2":
169171
# V2 denies invalid token
170172
run_guest_cmd(ssh_connection, cmd, "MMDS token not valid.")
173+
metrics = test_microvm.flush_metrics()
174+
assert metrics["mmds"]["rx_invalid_token"] == 1
171175

172176
# Get request with valid token
173177
token = generate_mmds_session_token(ssh_connection, DEFAULT_IPV4, 60, imds_compat)
174178
cmd = generate_mmds_get_request(DEFAULT_IPV4, token, False, imds_compat) + "foo"
175179
run_guest_cmd(ssh_connection, cmd, "bar")
180+
metrics = test_microvm.flush_metrics()
181+
assert metrics["mmds"]["rx_invalid_token"] == 0
176182

177183

178184
@pytest.mark.parametrize("version", MMDS_VERSIONS)

0 commit comments

Comments
 (0)