Skip to content

Commit 947b3af

Browse files
authored
[authz tests] Add coverage for support bundles (#9756)
Part of #9747
1 parent 5472a8a commit 947b3af

File tree

4 files changed

+75
-15
lines changed

4 files changed

+75
-15
lines changed

nexus/tests/integration_tests/audit_log.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ async fn test_audit_log_coverage(ctx: &ControlPlaneTestContext) {
452452
| AllowedMethod::GetNonexistent
453453
| AllowedMethod::GetUnimplemented
454454
| AllowedMethod::GetVolatile
455-
| AllowedMethod::GetWebsocket => false,
455+
| AllowedMethod::GetWebsocket
456+
| AllowedMethod::Head
457+
| AllowedMethod::HeadNonexistent => false,
456458
};
457459

458460
let before = fetch_log(client, t_start, None).await.items.len();

nexus/tests/integration_tests/endpoints.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ pub const SUPPORT_BUNDLES_URL: &'static str =
9595
"/experimental/v1/system/support-bundles";
9696
pub static SUPPORT_BUNDLE_URL: LazyLock<String> =
9797
LazyLock::new(|| format!("{SUPPORT_BUNDLES_URL}/{{id}}"));
98+
pub static SUPPORT_BUNDLE_DOWNLOAD_URL: LazyLock<String> =
99+
LazyLock::new(|| format!("{SUPPORT_BUNDLES_URL}/{{id}}/download"));
100+
pub static SUPPORT_BUNDLE_DOWNLOAD_FILE_URL: LazyLock<String> =
101+
LazyLock::new(|| {
102+
format!("{SUPPORT_BUNDLES_URL}/{{id}}/download/some-file.txt")
103+
});
104+
pub static SUPPORT_BUNDLE_INDEX_URL: LazyLock<String> =
105+
LazyLock::new(|| format!("{SUPPORT_BUNDLES_URL}/{{id}}/index"));
98106

99107
// Global policy
100108
pub const SYSTEM_POLICY_URL: &'static str = "/v1/system/policy";
@@ -1585,6 +1593,13 @@ pub enum AllowedMethod {
15851593
GetVolatile,
15861594
/// HTTP "GET" method with websocket handshake headers.
15871595
GetWebsocket,
1596+
/// HTTP "HEAD" method
1597+
#[allow(dead_code)]
1598+
Head,
1599+
/// HTTP "HEAD" method, but where we cannot statically define a URL that
1600+
/// will work (similar to GetNonexistent). The test runner should not expect
1601+
/// to get a 200 for privileged requests.
1602+
HeadNonexistent,
15881603
/// HTTP "POST" method, with sample input (which should be valid input for
15891604
/// this endpoint)
15901605
Post(serde_json::Value),
@@ -1603,6 +1618,9 @@ impl AllowedMethod {
16031618
| AllowedMethod::GetUnimplemented
16041619
| AllowedMethod::GetVolatile
16051620
| AllowedMethod::GetWebsocket => &Method::GET,
1621+
AllowedMethod::Head | AllowedMethod::HeadNonexistent => {
1622+
&Method::HEAD
1623+
}
16061624
AllowedMethod::Post(_) => &Method::POST,
16071625
AllowedMethod::Put(_) => &Method::PUT,
16081626
}
@@ -1619,7 +1637,9 @@ impl AllowedMethod {
16191637
| AllowedMethod::GetNonexistent
16201638
| AllowedMethod::GetUnimplemented
16211639
| AllowedMethod::GetVolatile
1622-
| AllowedMethod::GetWebsocket => None,
1640+
| AllowedMethod::GetWebsocket
1641+
| AllowedMethod::Head
1642+
| AllowedMethod::HeadNonexistent => None,
16231643
AllowedMethod::Post(body) => Some(&body),
16241644
AllowedMethod::Put(body) => Some(&body),
16251645
}
@@ -2850,6 +2870,34 @@ pub static VERIFY_ENDPOINTS: LazyLock<Vec<VerifyEndpoint>> = LazyLock::new(
28502870
),
28512871
],
28522872
},
2873+
// Note: We use GetNonexistent/HeadNonexistent because the bundle is
2874+
// created but not in "Active" state (which requires background task
2875+
// processing). Privileged GET/HEAD will fail with 400, not 200, so
2876+
// we skip that check.
2877+
VerifyEndpoint {
2878+
url: &SUPPORT_BUNDLE_DOWNLOAD_URL,
2879+
visibility: Visibility::Protected,
2880+
unprivileged_access: UnprivilegedAccess::None,
2881+
allowed_methods: vec![
2882+
AllowedMethod::GetNonexistent,
2883+
AllowedMethod::HeadNonexistent,
2884+
],
2885+
},
2886+
VerifyEndpoint {
2887+
url: &SUPPORT_BUNDLE_DOWNLOAD_FILE_URL,
2888+
visibility: Visibility::Protected,
2889+
unprivileged_access: UnprivilegedAccess::None,
2890+
allowed_methods: vec![
2891+
AllowedMethod::GetNonexistent,
2892+
AllowedMethod::HeadNonexistent,
2893+
],
2894+
},
2895+
VerifyEndpoint {
2896+
url: &SUPPORT_BUNDLE_INDEX_URL,
2897+
visibility: Visibility::Protected,
2898+
unprivileged_access: UnprivilegedAccess::None,
2899+
allowed_methods: vec![AllowedMethod::GetNonexistent],
2900+
},
28532901
/* Updates */
28542902
VerifyEndpoint {
28552903
url: DEMO_UPDATE_TRUST_ROOTS_URL,

nexus/tests/integration_tests/unauthorized.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,12 @@ static SETUP_REQUESTS: LazyLock<Vec<SetupReq>> = LazyLock::new(|| {
491491
},
492492
)
493493
.unwrap(),
494-
id_routes: vec!["/experimental/v1/system/support-bundles/{id}"],
494+
id_routes: vec![
495+
"/experimental/v1/system/support-bundles/{id}",
496+
"/experimental/v1/system/support-bundles/{id}/download",
497+
"/experimental/v1/system/support-bundles/{id}/download/some-file.txt",
498+
"/experimental/v1/system/support-bundles/{id}/index",
499+
],
495500
},
496501
// Create a trusted root for updates
497502
SetupReq::Post {
@@ -695,8 +700,14 @@ async fn verify_endpoint(
695700

696701
// For each of the HTTP methods we use in the API as well as TRACE, we'll
697702
// make several requests to this URL and verify the results.
698-
let methods =
699-
[Method::GET, Method::PUT, Method::POST, Method::DELETE, Method::TRACE];
703+
let methods = [
704+
Method::GET,
705+
Method::PUT,
706+
Method::POST,
707+
Method::DELETE,
708+
Method::HEAD,
709+
Method::TRACE,
710+
];
700711
for method in methods {
701712
let allowed = endpoint
702713
.allowed_methods
@@ -736,7 +747,7 @@ async fn verify_endpoint(
736747
let response = request.execute().await.unwrap_or_else(|e| {
737748
panic!("Failed making {method} request to {uri}: {e}")
738749
});
739-
verify_response(&response);
750+
verify_response(&response, &method);
740751
record_operation(WhichTest::Unprivileged(&expected_status));
741752
} else {
742753
// "This door is opened elsewhere."
@@ -757,7 +768,7 @@ async fn verify_endpoint(
757768
request = request.expect_websocket_handshake();
758769
}
759770
let response = request.execute().await.unwrap();
760-
verify_response(&response);
771+
verify_response(&response, &method);
761772
record_operation(WhichTest::Unauthenticated(&expected_status));
762773

763774
// Now try a few requests with bogus credentials. We should get the
@@ -790,7 +801,7 @@ async fn verify_endpoint(
790801
request = request.expect_websocket_handshake();
791802
}
792803
let response = request.execute().await.unwrap();
793-
verify_response(&response);
804+
verify_response(&response, &method);
794805
record_operation(WhichTest::UnknownUser(&expected_status));
795806

796807
// Now try a syntactically invalid authn header.
@@ -808,7 +819,7 @@ async fn verify_endpoint(
808819
request = request.expect_websocket_handshake();
809820
}
810821
let response = request.execute().await.unwrap();
811-
verify_response(&response);
822+
verify_response(&response, &method);
812823
record_operation(WhichTest::InvalidHeader(&expected_status));
813824

814825
print!(" ");
@@ -851,11 +862,15 @@ async fn verify_endpoint(
851862
}
852863

853864
/// Verifies the body of an HTTP response for status codes 401, 403, 404, or 405
854-
fn verify_response(response: &TestResponse) {
865+
fn verify_response(response: &TestResponse, method: &Method) {
855866
if response.status == StatusCode::SWITCHING_PROTOCOLS {
856867
// websocket handshake. avoid trying to parse absent body as json.
857868
return;
858869
}
870+
if *method == Method::HEAD {
871+
// HEAD requests have no body to parse.
872+
return;
873+
}
859874
let error: HttpErrorResponseBody = response.parsed_body().unwrap();
860875
match response.status {
861876
StatusCode::UNAUTHORIZED => {

nexus/tests/output/uncovered-authz-endpoints.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@ probe_delete (delete "/experimental/v1/probes/{probe
33
current_user_access_token_delete (delete "/v1/me/access-tokens/{token_id}")
44
probe_list (get "/experimental/v1/probes")
55
probe_view (get "/experimental/v1/probes/{probe}")
6-
support_bundle_download (get "/experimental/v1/system/support-bundles/{bundle_id}/download")
7-
support_bundle_download_file (get "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}")
8-
support_bundle_index (get "/experimental/v1/system/support-bundles/{bundle_id}/index")
96
ping (get "/v1/ping")
107
networking_switch_port_lldp_neighbors (get "/v1/system/hardware/rack-switch-port/{rack_id}/{switch_location}/{port}/lldp/neighbors")
118
rack_membership_status (get "/v1/system/hardware/racks/{rack_id}/membership")
129
networking_switch_port_lldp_config_view (get "/v1/system/hardware/switch-port/{port}/lldp/config")
1310
networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status")
14-
support_bundle_head (head "/experimental/v1/system/support-bundles/{bundle_id}/download")
15-
support_bundle_head_file (head "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}")
1611
device_auth_request (post "/device/auth")
1712
device_auth_confirm (post "/device/confirm")
1813
device_access_token (post "/device/token")

0 commit comments

Comments
 (0)