Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions src/api/endpoints/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ pub async fn get_cloudformation_template(
client.execute(req).await
}

/// Generate a temporary integration token.
///
/// With `Some(integration_id)` the token is scoped to an existing integration
/// (the `plerion aws generate-token` behavior). With `None` the request is a
/// bare POST, which mints the onboarding token used to register a NEW AWS
/// account integration via CloudFormation.
pub async fn generate_token(
client: &PlerionClient,
integration_id: &str,
integration_id: Option<&str>,
) -> Result<serde_json::Value, PlerionError> {
client
.execute(
client
.post("/v1/tenant/integrations/token")
.json(&serde_json::json!({ "integrationId": integration_id })),
)
.await
let req = client.post("/v1/tenant/integrations/token");
let req = match integration_id {
Some(id) => req.json(&serde_json::json!({ "integrationId": id })),
None => req,
};
client.execute(req).await
}
47 changes: 47 additions & 0 deletions src/api/models/aws.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Constructed via deserialization by the onboarding engine; the binary
// doesn't reference these until `integrations add aws` lands (follow-up PR).
#![allow(dead_code)]

use serde::{Deserialize, Serialize};

/// Typed envelopes for the AWS integration endpoints, used by the onboarding
/// orchestrator. The raw `plerion aws ...` subcommands keep returning
/// `serde_json::Value` so their passthrough output is unchanged.

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalIdResponse {
pub data: Option<ExternalIdData>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExternalIdData {
pub external_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfnTemplateResponse {
pub data: Option<CfnTemplateData>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CfnTemplateData {
#[serde(rename = "templateURL")]
pub template_url: Option<String>,
pub template_version: Option<String>,
/// Not returned by the API today; picked up automatically if/when the
/// tenant's Plerion-managed CWPP service account ID is exposed here.
pub service_account_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenResponse {
pub data: Option<TokenData>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenData {
pub token: Option<String>,
}
1 change: 1 addition & 0 deletions src/api/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod alerts;
pub mod asset_groups;
pub mod assets;
pub mod audit_logs;
pub mod aws;
pub mod compliance;
pub mod findings;
pub mod iac;
Expand Down
2 changes: 1 addition & 1 deletion src/cli/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub async fn run(args: &AwsArgs, config: &Config) -> anyhow::Result<()> {
output::render_json_value(&resp, config.output, config.query.as_deref())?;
}
AwsCommands::GenerateToken { integration_id } => {
let resp = generate_token(&client, integration_id).await?;
let resp = generate_token(&client, Some(integration_id)).await?;
output::render_json_value(&resp, config.output, config.query.as_deref())?;
}
}
Expand Down
26 changes: 24 additions & 2 deletions tests/aws_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,44 @@ async fn test_get_cloudformation_template() {
}

#[tokio::test]
async fn test_generate_token() {
async fn test_generate_token_for_integration() {
let mut server = Server::new_async().await;
let body = serde_json::json!({ "data": { "token": "tmp-token-xyz" } });
let mock = server
.mock("POST", "/v1/tenant/integrations/token")
.match_body(mockito::Matcher::JsonString(
r#"{"integrationId":"int-123"}"#.to_string(),
))
.with_status(200)
.with_body(body.to_string())
.create_async()
.await;

let client = PlerionClient::with_base_url(&server.url(), "key").unwrap();
let resp = aws::generate_token(&client, "int-123").await.unwrap();
let resp = aws::generate_token(&client, Some("int-123")).await.unwrap();
assert_eq!(resp["data"]["token"], "tmp-token-xyz");
mock.assert_async().await;
}

#[tokio::test]
async fn test_generate_token_bare_post_for_onboarding() {
let mut server = Server::new_async().await;
let body = serde_json::json!({ "data": { "token": "tmp-onboard-token" } });
// The onboarding variant must NOT send an integrationId body.
let mock = server
.mock("POST", "/v1/tenant/integrations/token")
.match_body(mockito::Matcher::Exact(String::new()))
.with_status(200)
.with_body(body.to_string())
.create_async()
.await;

let client = PlerionClient::with_base_url(&server.url(), "key").unwrap();
let resp = aws::generate_token(&client, None).await.unwrap();
assert_eq!(resp["data"]["token"], "tmp-onboard-token");
mock.assert_async().await;
}

#[tokio::test]
async fn test_get_external_id_forbidden() {
let mut server = Server::new_async().await;
Expand Down