From 99bd26f729613c15da3b00f59f47a4ade5139c9b Mon Sep 17 00:00:00 2001 From: "ci.datadog-api-spec" Date: Fri, 24 Jul 2026 17:30:10 +0000 Subject: [PATCH] Regenerate client from commit 71bc35a of spec repo --- .generator/schemas/v2/openapi.yaml | 333 ++++++++++++++++ ...reat-intelligence_IngestStixThreatIntel.rs | 51 +++ src/datadog/configuration.rs | 1 + src/datadogV2/api/api_threat_intelligence.rs | 309 +++++++++++++++ src/datadogV2/api/mod.rs | 1 + src/datadogV2/mod.rs | 1 + src/datadogV2/model/mod.rs | 22 ++ .../model/model_stix_bundle_request.rs | 154 ++++++++ src/datadogV2/model/model_stix_bundle_type.rs | 48 +++ .../model/model_stix_content_encoding.rs | 48 +++ .../model/model_stix_indicator_object.rs | 356 ++++++++++++++++++ .../model/model_stix_indicator_type.rs | 48 +++ .../model/model_stix_ingest_response.rs | 92 +++++ .../model_stix_ingest_response_attributes.rs | 114 ++++++ .../model/model_stix_ingest_response_data.rs | 125 ++++++ .../model/model_stix_ingest_response_type.rs | 48 +++ .../model/model_stix_pattern_type.rs | 48 +++ .../model/model_stix_spec_version.rs | 48 +++ ...at-intelligence-returns-OK-response.frozen | 1 + ...reat-intelligence-returns-OK-response.json | 39 ++ .../features/v2/threat_intelligence.feature | 37 ++ tests/scenarios/features/v2/undo.json | 6 + tests/scenarios/function_mappings.rs | 50 +++ 23 files changed, 1980 insertions(+) create mode 100644 examples/v2_threat-intelligence_IngestStixThreatIntel.rs create mode 100644 src/datadogV2/api/api_threat_intelligence.rs create mode 100644 src/datadogV2/model/model_stix_bundle_request.rs create mode 100644 src/datadogV2/model/model_stix_bundle_type.rs create mode 100644 src/datadogV2/model/model_stix_content_encoding.rs create mode 100644 src/datadogV2/model/model_stix_indicator_object.rs create mode 100644 src/datadogV2/model/model_stix_indicator_type.rs create mode 100644 src/datadogV2/model/model_stix_ingest_response.rs create mode 100644 src/datadogV2/model/model_stix_ingest_response_attributes.rs create mode 100644 src/datadogV2/model/model_stix_ingest_response_data.rs create mode 100644 src/datadogV2/model/model_stix_ingest_response_type.rs create mode 100644 src/datadogV2/model/model_stix_pattern_type.rs create mode 100644 src/datadogV2/model/model_stix_spec_version.rs create mode 100644 tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.frozen create mode 100644 tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.json create mode 100644 tests/scenarios/features/v2/threat_intelligence.feature diff --git a/.generator/schemas/v2/openapi.yaml b/.generator/schemas/v2/openapi.yaml index 203a2ac6c..2f8c17f54 100644 --- a/.generator/schemas/v2/openapi.yaml +++ b/.generator/schemas/v2/openapi.yaml @@ -86174,6 +86174,229 @@ components: example: "report_id" type: string type: object + STIXBundleRequest: + additionalProperties: {} + description: A STIX 2.1 bundle containing threat intelligence indicator objects. + properties: + id: + description: The STIX bundle identifier. + example: bundle--11111111-1111-4111-8111-111111111111 + type: string + objects: + description: The indicator objects included in the bundle. + example: + - created: "2026-07-22T12:00:00Z" + id: indicator--22222222-2222-4222-8222-222222222222 + modified: "2026-07-22T12:00:00Z" + pattern: "[ipv4-addr:value = '198.51.100.42']" + pattern_type: stix + spec_version: "2.1" + type: indicator + valid_from: "2026-07-22T12:00:00Z" + items: + $ref: "#/components/schemas/STIXIndicatorObject" + type: array + spec_version: + $ref: "#/components/schemas/STIXSpecVersion" + type: + $ref: "#/components/schemas/STIXBundleType" + required: + - type + - id + - objects + type: object + STIXBundleType: + description: The STIX object type for a bundle. + enum: + - bundle + example: bundle + type: string + x-enum-varnames: + - BUNDLE + STIXContentEncoding: + description: The content encoding applied to the request body. + enum: + - gzip + example: gzip + type: string + x-enum-varnames: + - GZIP + STIXIndicatorObject: + additionalProperties: {} + description: A STIX 2.1 indicator object. + properties: + confidence: + description: The confidence in the correctness of the indicator, from 0 through 100. + example: 80 + format: int32 + maximum: 100 + minimum: 0 + type: integer + created: + description: The time when the indicator was created. + example: "2026-07-22T12:00:00Z" + format: date-time + type: string + external_references: + description: Optional external reference metadata preserved with the indicator but not interpreted during ingestion. + items: + $ref: "#/components/schemas/STIXMetadataObject" + type: array + id: + description: The STIX indicator identifier. + example: indicator--22222222-2222-4222-8222-222222222222 + type: string + indicator_types: + description: The open vocabulary terms that categorize the indicator. + example: + - malicious-activity + items: + type: string + type: array + kill_chain_phases: + description: Optional kill chain metadata preserved with the indicator but not interpreted during ingestion. + items: + $ref: "#/components/schemas/STIXMetadataObject" + type: array + labels: + description: Labels associated with the indicator. + example: + - synthetic + items: + type: string + type: array + modified: + description: The time when the indicator was last modified. + example: "2026-07-22T12:00:00Z" + format: date-time + type: string + object_marking_refs: + description: References to marking definition objects that apply to the indicator. + example: + - marking-definition--33333333-3333-4333-8333-333333333333 + items: + type: string + type: array + pattern: + description: The STIX pattern that identifies the observable. + example: "[ipv4-addr:value = '198.51.100.42']" + type: string + pattern_type: + $ref: "#/components/schemas/STIXPatternType" + revoked: + description: Whether the indicator has been revoked. + example: false + type: boolean + spec_version: + $ref: "#/components/schemas/STIXSpecVersion" + type: + $ref: "#/components/schemas/STIXIndicatorType" + valid_from: + description: The time from which the indicator is considered valid. + example: "2026-07-22T12:00:00Z" + format: date-time + type: string + valid_until: + description: The time until which the indicator is considered valid. + example: "2027-07-22T12:00:00Z" + format: date-time + type: string + required: + - type + - id + - spec_version + - created + - modified + - pattern + - pattern_type + - valid_from + type: object + STIXIndicatorType: + description: The STIX object type for an indicator. + enum: + - indicator + example: indicator + type: string + x-enum-varnames: + - INDICATOR + STIXIngestResponse: + description: The response from a completed STIX ingestion request. + properties: + data: + $ref: "#/components/schemas/STIXIngestResponseData" + required: + - data + type: object + STIXIngestResponseAttributes: + description: Counters describing the result of the STIX ingestion request. + properties: + added: + description: The number of supported indicators added. + example: 1 + format: int64 + minimum: 0 + type: integer + invalid: + description: The number of indicators with patterns that could not be parsed. + example: 0 + format: int64 + minimum: 0 + type: integer + unsupported: + description: The number of unsupported objects or patterns. + example: 0 + format: int64 + minimum: 0 + type: integer + required: + - added + - unsupported + - invalid + type: object + STIXIngestResponseData: + description: The JSON:API resource describing the completed STIX ingestion request. + properties: + attributes: + $ref: "#/components/schemas/STIXIngestResponseAttributes" + id: + description: The normalized vendor identifier. + example: acme + type: string + type: + $ref: "#/components/schemas/STIXIngestResponseType" + required: + - type + - id + - attributes + type: object + STIXIngestResponseType: + description: The STIX ingestion resource type. + enum: + - threat-intel-stix-ingest + example: threat-intel-stix-ingest + type: string + x-enum-varnames: + - THREAT_INTEL_STIX_INGEST + STIXMetadataObject: + additionalProperties: {} + description: An opaque STIX metadata object. + type: object + STIXPatternType: + description: The supported STIX pattern language. + enum: + - stix + example: stix + type: string + x-enum-varnames: + - STIX + STIXSpecVersion: + description: The supported STIX specification version. + enum: + - "2.1" + example: "2.1" + type: string + x-enum-varnames: + - VERSION_2_1 SalesforceIncidentsOrganizationResponseAttributes: description: Attributes of a Salesforce organization connected to the Datadog Salesforce integration. properties: @@ -181627,6 +181850,114 @@ paths: operator: OR permissions: - security_monitoring_notification_profiles_write + /api/v2/security/threat-intel/stix: + post: + description: |- + Ingest a STIX 2.1 bundle containing threat intelligence indicators. Only indicator objects are supported. Supported indicator patterns contain IPv4 addresses, IPv6 addresses, domain names, or SHA-256 file hashes. + + Unsupported objects and patterns increment the `unsupported` counter. Patterns that cannot be parsed increment the `invalid` counter. Processing is best effort, so valid supported indicators in the same bundle are still added. + + A successful response means ingestion has completed. Reference-table materialization and enrichment happen asynchronously. Requests are limited to 50 MB and 10 requests per second per API key. Gzip-compressed request bodies are supported. + operationId: IngestStixThreatIntel + parameters: + - description: >- + Vendor identifier for the feed. The value must not exceed 10 characters. Datadog normalizes the accepted value to lowercase, converts non-alphanumeric characters to underscores, and trims leading and trailing underscores. + example: acme + in: header + name: ti_vendor + required: true + schema: + maxLength: 10 + minLength: 1 + type: string + - description: Content encoding for the request body. Use gzip for a compressed STIX bundle. + in: header + name: Content-Encoding + required: false + schema: + $ref: "#/components/schemas/STIXContentEncoding" + requestBody: + content: + application/json: + examples: + default: + value: + id: bundle--11111111-1111-4111-8111-111111111111 + objects: + - created: "2026-07-22T12:00:00Z" + id: indicator--22222222-2222-4222-8222-222222222222 + modified: "2026-07-22T12:00:00Z" + pattern: "[ipv4-addr:value = '198.51.100.42']" + pattern_type: stix + spec_version: "2.1" + type: indicator + valid_from: "2026-07-22T12:00:00Z" + spec_version: "2.1" + type: bundle + schema: + $ref: "#/components/schemas/STIXBundleRequest" + description: >- + A STIX 2.1 bundle containing indicator objects. The maximum request size is 50 MB. When `Content-Encoding: gzip` is used, the limit applies to the compressed bytes received. + required: true + responses: + "200": + content: + application/json: + examples: + default: + value: + data: + attributes: + added: 1 + invalid: 0 + unsupported: 0 + id: acme + type: threat-intel-stix-ingest + schema: + $ref: "#/components/schemas/STIXIngestResponse" + description: OK + "400": + $ref: "#/components/responses/BadRequestResponse" + "401": + $ref: "#/components/responses/UnauthorizedResponse" + "403": + $ref: "#/components/responses/ForbiddenResponse" + "413": + content: + application/json: + schema: + $ref: "#/components/schemas/APIErrorResponse" + description: Payload Too Large + "429": + $ref: "#/components/responses/TooManyRequestsResponse" + "502": + content: + application/json: + schema: + $ref: "#/components/schemas/APIErrorResponse" + description: Bad Gateway + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/APIErrorResponse" + description: Service Unavailable + security: + - apiKeyAuth: [] + appKeyAuth: [] + - AuthZ: + - reference_tables_write + summary: Ingest STIX threat intelligence + tags: + - Threat Intelligence + x-codegen-request-body-name: body + x-permission: + operator: OR + permissions: + - reference_tables_write + x-unstable: |- + **Note**: This endpoint is in preview and is subject to change. + If you have any feedback, contact [Datadog support](https://docs.datadoghq.com/help/). /api/v2/security/vulnerabilities: get: deprecated: true @@ -204776,6 +205107,8 @@ tags: - description: |- Search and manage flaky tests through Test Optimization. See the [Test Optimization page](https://docs.datadoghq.com/tests/) for more information. name: Test Optimization + - description: Ingest and manage threat intelligence data for security enrichment and investigation. + name: Threat Intelligence - description: |- The usage metering API allows you to get hourly, daily, and monthly usage across multiple facets of Datadog. diff --git a/examples/v2_threat-intelligence_IngestStixThreatIntel.rs b/examples/v2_threat-intelligence_IngestStixThreatIntel.rs new file mode 100644 index 000000000..eaba788bb --- /dev/null +++ b/examples/v2_threat-intelligence_IngestStixThreatIntel.rs @@ -0,0 +1,51 @@ +// Ingest STIX threat intelligence returns "OK" response +use chrono::{DateTime, Utc}; +use datadog_api_client::datadog; +use datadog_api_client::datadogV2::api_threat_intelligence::IngestStixThreatIntelOptionalParams; +use datadog_api_client::datadogV2::api_threat_intelligence::ThreatIntelligenceAPI; +use datadog_api_client::datadogV2::model::STIXBundleRequest; +use datadog_api_client::datadogV2::model::STIXBundleType; +use datadog_api_client::datadogV2::model::STIXIndicatorObject; +use datadog_api_client::datadogV2::model::STIXIndicatorType; +use datadog_api_client::datadogV2::model::STIXPatternType; +use datadog_api_client::datadogV2::model::STIXSpecVersion; + +#[tokio::main] +async fn main() { + let body = STIXBundleRequest::new( + "bundle--44444444-4444-4444-8444-444444444444".to_string(), + vec![STIXIndicatorObject::new( + DateTime::parse_from_rfc3339("2026-07-22T12:00:00+00:00") + .expect("Failed to parse datetime") + .with_timezone(&Utc), + "indicator--55555555-5555-4555-8555-555555555555".to_string(), + DateTime::parse_from_rfc3339("2026-07-22T12:00:00+00:00") + .expect("Failed to parse datetime") + .with_timezone(&Utc), + "[ipv4-addr:value = '198.51.100.42']".to_string(), + STIXPatternType::STIX, + STIXSpecVersion::VERSION_2_1, + STIXIndicatorType::INDICATOR, + DateTime::parse_from_rfc3339("2026-07-22T12:00:00+00:00") + .expect("Failed to parse datetime") + .with_timezone(&Utc), + )], + STIXBundleType::BUNDLE, + ) + .spec_version(STIXSpecVersion::VERSION_2_1); + let mut configuration = datadog::Configuration::new(); + configuration.set_unstable_operation_enabled("v2.IngestStixThreatIntel", true); + let api = ThreatIntelligenceAPI::with_config(configuration); + let resp = api + .ingest_stix_threat_intel( + "Acme-Inc".to_string(), + body, + IngestStixThreatIntelOptionalParams::default(), + ) + .await; + if let Ok(value) = resp { + println!("{:#?}", value); + } else { + println!("{:#?}", resp.unwrap_err()); + } +} diff --git a/src/datadog/configuration.rs b/src/datadog/configuration.rs index abd6cc733..8d4df10dd 100644 --- a/src/datadog/configuration.rs +++ b/src/datadog/configuration.rs @@ -732,6 +732,7 @@ impl Default for Configuration { ("v2.create_scorecard_outcomes_batch".to_owned(), false), ("v2.get_entity_risk_score".to_owned(), false), ("v2.list_entity_risk_scores".to_owned(), false), + ("v2.ingest_stix_threat_intel".to_owned(), false), ("v2.create_slo_report_job".to_owned(), false), ("v2.get_slo_report".to_owned(), false), ("v2.get_slo_report_job_status".to_owned(), false), diff --git a/src/datadogV2/api/api_threat_intelligence.rs b/src/datadogV2/api/api_threat_intelligence.rs new file mode 100644 index 000000000..9b222ac40 --- /dev/null +++ b/src/datadogV2/api/api_threat_intelligence.rs @@ -0,0 +1,309 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use crate::datadog; +use flate2::{ + write::{GzEncoder, ZlibEncoder}, + Compression, +}; +use log::warn; +use reqwest::header::{HeaderMap, HeaderValue}; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +/// IngestStixThreatIntelOptionalParams is a struct for passing parameters to the method [`ThreatIntelligenceAPI::ingest_stix_threat_intel`] +#[non_exhaustive] +#[derive(Clone, Default, Debug)] +pub struct IngestStixThreatIntelOptionalParams { + /// Content encoding for the request body. Use gzip for a compressed STIX bundle. + pub content_encoding: Option, +} + +impl IngestStixThreatIntelOptionalParams { + /// Content encoding for the request body. Use gzip for a compressed STIX bundle. + pub fn content_encoding(mut self, value: crate::datadogV2::model::STIXContentEncoding) -> Self { + self.content_encoding = Some(value); + self + } +} + +/// IngestStixThreatIntelError is a struct for typed errors of method [`ThreatIntelligenceAPI::ingest_stix_threat_intel`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum IngestStixThreatIntelError { + APIErrorResponse(crate::datadogV2::model::APIErrorResponse), + UnknownValue(serde_json::Value), +} + +/// Ingest and manage threat intelligence data for security enrichment and investigation. +#[derive(Debug, Clone)] +pub struct ThreatIntelligenceAPI { + config: datadog::Configuration, + client: reqwest_middleware::ClientWithMiddleware, +} + +impl Default for ThreatIntelligenceAPI { + fn default() -> Self { + Self::with_config(datadog::Configuration::default()) + } +} + +impl ThreatIntelligenceAPI { + pub fn new() -> Self { + Self::default() + } + pub fn with_config(config: datadog::Configuration) -> Self { + let reqwest_client_builder = { + let builder = reqwest::Client::builder(); + #[cfg(not(target_arch = "wasm32"))] + let builder = if let Some(proxy_url) = &config.proxy_url { + builder.proxy(reqwest::Proxy::all(proxy_url).expect("Failed to parse proxy URL")) + } else { + builder + }; + builder + }; + + let middleware_client_builder = { + let builder = + reqwest_middleware::ClientBuilder::new(reqwest_client_builder.build().unwrap()); + #[cfg(feature = "retry")] + let builder = if config.enable_retry { + struct RetryableStatus; + impl reqwest_retry::RetryableStrategy for RetryableStatus { + fn handle( + &self, + res: &Result, + ) -> Option { + match res { + Ok(success) => reqwest_retry::default_on_request_success(success), + Err(_) => None, + } + } + } + let backoff_policy = reqwest_retry::policies::ExponentialBackoff::builder() + .build_with_max_retries(config.max_retries); + + let retry_middleware = + reqwest_retry::RetryTransientMiddleware::new_with_policy_and_strategy( + backoff_policy, + RetryableStatus, + ); + + builder.with(retry_middleware) + } else { + builder + }; + builder + }; + + let client = middleware_client_builder.build(); + + Self { config, client } + } + + pub fn with_client_and_config( + config: datadog::Configuration, + client: reqwest_middleware::ClientWithMiddleware, + ) -> Self { + Self { config, client } + } + + /// Ingest a STIX 2.1 bundle containing threat intelligence indicators. Only indicator objects are supported. Supported indicator patterns contain IPv4 addresses, IPv6 addresses, domain names, or SHA-256 file hashes. + /// + /// Unsupported objects and patterns increment the `unsupported` counter. Patterns that cannot be parsed increment the `invalid` counter. Processing is best effort, so valid supported indicators in the same bundle are still added. + /// + /// A successful response means ingestion has completed. Reference-table materialization and enrichment happen asynchronously. Requests are limited to 50 MB and 10 requests per second per API key. Gzip-compressed request bodies are supported. + pub async fn ingest_stix_threat_intel( + &self, + ti_vendor: String, + body: crate::datadogV2::model::STIXBundleRequest, + params: IngestStixThreatIntelOptionalParams, + ) -> Result< + crate::datadogV2::model::STIXIngestResponse, + datadog::Error, + > { + match self + .ingest_stix_threat_intel_with_http_info(ti_vendor, body, params) + .await + { + Ok(response_content) => { + if let Some(e) = response_content.entity { + Ok(e) + } else { + Err(datadog::Error::Serde(serde::de::Error::custom( + "response content was None", + ))) + } + } + Err(err) => Err(err), + } + } + + /// Ingest a STIX 2.1 bundle containing threat intelligence indicators. Only indicator objects are supported. Supported indicator patterns contain IPv4 addresses, IPv6 addresses, domain names, or SHA-256 file hashes. + /// + /// Unsupported objects and patterns increment the `unsupported` counter. Patterns that cannot be parsed increment the `invalid` counter. Processing is best effort, so valid supported indicators in the same bundle are still added. + /// + /// A successful response means ingestion has completed. Reference-table materialization and enrichment happen asynchronously. Requests are limited to 50 MB and 10 requests per second per API key. Gzip-compressed request bodies are supported. + pub async fn ingest_stix_threat_intel_with_http_info( + &self, + ti_vendor: String, + body: crate::datadogV2::model::STIXBundleRequest, + params: IngestStixThreatIntelOptionalParams, + ) -> Result< + datadog::ResponseContent, + datadog::Error, + > { + let local_configuration = &self.config; + let operation_id = "v2.ingest_stix_threat_intel"; + if local_configuration.is_unstable_operation_enabled(operation_id) { + warn!("Using unstable operation {operation_id}"); + } else { + let local_error = datadog::UnstableOperationDisabledError { + msg: "Operation 'v2.ingest_stix_threat_intel' is not enabled".to_string(), + }; + return Err(datadog::Error::UnstableOperationDisabledError(local_error)); + } + + // unbox and build optional parameters + let content_encoding = params.content_encoding; + + let local_client = &self.client; + + let local_uri_str = format!( + "{}/api/v2/security/threat-intel/stix", + local_configuration.get_operation_host(operation_id) + ); + let mut local_req_builder = + local_client.request(reqwest::Method::POST, local_uri_str.as_str()); + + // build headers + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", HeaderValue::from_static("application/json")); + headers.insert("Accept", HeaderValue::from_static("application/json")); + + headers.insert( + "ti_vendor", + ti_vendor + .to_string() + .parse() + .expect("failed to parse ti_vendor header"), + ); + if let Some(ref local) = content_encoding { + headers.insert( + "Content-Encoding", + local + .to_string() + .parse() + .expect("failed to parse Content-Encoding header"), + ); + } + + // build user agent + match HeaderValue::from_str(local_configuration.user_agent.as_str()) { + Ok(user_agent) => headers.insert(reqwest::header::USER_AGENT, user_agent), + Err(e) => { + log::warn!("Failed to parse user agent header: {e}, falling back to default"); + headers.insert( + reqwest::header::USER_AGENT, + HeaderValue::from_static(datadog::DEFAULT_USER_AGENT.as_str()), + ) + } + }; + + // build auth + if let Some(local_key) = local_configuration.auth_keys.get("apiKeyAuth") { + headers.insert( + "DD-API-KEY", + HeaderValue::from_str(local_key.key.as_str()) + .expect("failed to parse DD-API-KEY header"), + ); + }; + if let Some(local_key) = local_configuration.auth_keys.get("appKeyAuth") { + headers.insert( + "DD-APPLICATION-KEY", + HeaderValue::from_str(local_key.key.as_str()) + .expect("failed to parse DD-APPLICATION-KEY header"), + ); + }; + + // build body parameters + let output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(output, datadog::DDFormatter); + if body.serialize(&mut ser).is_ok() { + if let Some(content_encoding) = headers.get("Content-Encoding") { + match content_encoding.to_str().unwrap_or_default() { + "gzip" => { + let mut enc = GzEncoder::new(Vec::new(), Compression::default()); + let _ = enc.write_all(ser.into_inner().as_slice()); + match enc.finish() { + Ok(buf) => { + local_req_builder = local_req_builder.body(buf); + } + Err(e) => return Err(datadog::Error::Io(e)), + } + } + "deflate" => { + let mut enc = ZlibEncoder::new(Vec::new(), Compression::default()); + let _ = enc.write_all(ser.into_inner().as_slice()); + match enc.finish() { + Ok(buf) => { + local_req_builder = local_req_builder.body(buf); + } + Err(e) => return Err(datadog::Error::Io(e)), + } + } + #[cfg(feature = "zstd")] + "zstd1" => { + let mut enc = zstd::stream::Encoder::new(Vec::new(), 0).unwrap(); + let _ = enc.write_all(ser.into_inner().as_slice()); + match enc.finish() { + Ok(buf) => { + local_req_builder = local_req_builder.body(buf); + } + Err(e) => return Err(datadog::Error::Io(e)), + } + } + _ => { + local_req_builder = local_req_builder.body(ser.into_inner()); + } + } + } else { + local_req_builder = local_req_builder.body(ser.into_inner()); + } + } + + local_req_builder = local_req_builder.headers(headers); + let local_req = local_req_builder.build()?; + log::debug!("request content: {:?}", local_req.body()); + let local_resp = local_client.execute(local_req).await?; + + let local_status = local_resp.status(); + let local_content = local_resp.text().await?; + log::debug!("response content: {}", local_content); + + if !local_status.is_client_error() && !local_status.is_server_error() { + match serde_json::from_str::( + &local_content, + ) { + Ok(e) => { + return Ok(datadog::ResponseContent { + status: local_status, + content: local_content, + entity: Some(e), + }) + } + Err(e) => return Err(datadog::Error::Serde(e)), + }; + } else { + let local_entity: Option = + serde_json::from_str(&local_content).ok(); + let local_error = datadog::ResponseContent { + status: local_status, + content: local_content, + entity: local_entity, + }; + Err(datadog::Error::ResponseError(local_error)) + } + } +} diff --git a/src/datadogV2/api/mod.rs b/src/datadogV2/api/mod.rs index 1bb4db19d..e860395ea 100644 --- a/src/datadogV2/api/mod.rs +++ b/src/datadogV2/api/mod.rs @@ -134,6 +134,7 @@ pub mod api_synthetics; pub mod api_tag_policies; pub mod api_teams; pub mod api_test_optimization; +pub mod api_threat_intelligence; pub mod api_usage_metering; pub mod api_user_authorized_clients; pub mod api_users; diff --git a/src/datadogV2/mod.rs b/src/datadogV2/mod.rs index cf3a37a3a..9ae9c61bf 100644 --- a/src/datadogV2/mod.rs +++ b/src/datadogV2/mod.rs @@ -135,6 +135,7 @@ pub use self::api::api_synthetics; pub use self::api::api_tag_policies; pub use self::api::api_teams; pub use self::api::api_test_optimization; +pub use self::api::api_threat_intelligence; pub use self::api::api_usage_metering; pub use self::api::api_user_authorized_clients; pub use self::api::api_users; diff --git a/src/datadogV2/model/mod.rs b/src/datadogV2/model/mod.rs index 1873c6ec3..5c7c0d4dd 100644 --- a/src/datadogV2/model/mod.rs +++ b/src/datadogV2/model/mod.rs @@ -10746,6 +10746,28 @@ pub mod model_patch_notification_rule_parameters_data; pub use self::model_patch_notification_rule_parameters_data::PatchNotificationRuleParametersData; pub mod model_patch_notification_rule_parameters_data_attributes; pub use self::model_patch_notification_rule_parameters_data_attributes::PatchNotificationRuleParametersDataAttributes; +pub mod model_stix_content_encoding; +pub use self::model_stix_content_encoding::STIXContentEncoding; +pub mod model_stix_bundle_request; +pub use self::model_stix_bundle_request::STIXBundleRequest; +pub mod model_stix_indicator_object; +pub use self::model_stix_indicator_object::STIXIndicatorObject; +pub mod model_stix_pattern_type; +pub use self::model_stix_pattern_type::STIXPatternType; +pub mod model_stix_spec_version; +pub use self::model_stix_spec_version::STIXSpecVersion; +pub mod model_stix_indicator_type; +pub use self::model_stix_indicator_type::STIXIndicatorType; +pub mod model_stix_bundle_type; +pub use self::model_stix_bundle_type::STIXBundleType; +pub mod model_stix_ingest_response; +pub use self::model_stix_ingest_response::STIXIngestResponse; +pub mod model_stix_ingest_response_data; +pub use self::model_stix_ingest_response_data::STIXIngestResponseData; +pub mod model_stix_ingest_response_attributes; +pub use self::model_stix_ingest_response_attributes::STIXIngestResponseAttributes; +pub mod model_stix_ingest_response_type; +pub use self::model_stix_ingest_response_type::STIXIngestResponseType; pub mod model_vulnerability_type; pub use self::model_vulnerability_type::VulnerabilityType; pub mod model_vulnerability_severity; diff --git a/src/datadogV2/model/model_stix_bundle_request.rs b/src/datadogV2/model/model_stix_bundle_request.rs new file mode 100644 index 000000000..628642db2 --- /dev/null +++ b/src/datadogV2/model/model_stix_bundle_request.rs @@ -0,0 +1,154 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// A STIX 2.1 bundle containing threat intelligence indicator objects. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct STIXBundleRequest { + /// The STIX bundle identifier. + #[serde(rename = "id")] + pub id: String, + /// The indicator objects included in the bundle. + #[serde(rename = "objects")] + pub objects: Vec, + /// The supported STIX specification version. + #[serde(rename = "spec_version")] + pub spec_version: Option, + /// The STIX object type for a bundle. + #[serde(rename = "type")] + pub type_: crate::datadogV2::model::STIXBundleType, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl STIXBundleRequest { + pub fn new( + id: String, + objects: Vec, + type_: crate::datadogV2::model::STIXBundleType, + ) -> STIXBundleRequest { + STIXBundleRequest { + id, + objects, + spec_version: None, + type_, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn spec_version(mut self, value: crate::datadogV2::model::STIXSpecVersion) -> Self { + self.spec_version = Some(value); + self + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for STIXBundleRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct STIXBundleRequestVisitor; + impl<'a> Visitor<'a> for STIXBundleRequestVisitor { + type Value = STIXBundleRequest; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut id: Option = None; + let mut objects: Option> = None; + let mut spec_version: Option = None; + let mut type_: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "id" => { + id = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "objects" => { + objects = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "spec_version" => { + if v.is_null() { + continue; + } + spec_version = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _spec_version) = spec_version { + match _spec_version { + crate::datadogV2::model::STIXSpecVersion::UnparsedObject( + _spec_version, + ) => { + _unparsed = true; + } + _ => {} + } + } + } + "type" => { + type_ = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _type_) = type_ { + match _type_ { + crate::datadogV2::model::STIXBundleType::UnparsedObject( + _type_, + ) => { + _unparsed = true; + } + _ => {} + } + } + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let id = id.ok_or_else(|| M::Error::missing_field("id"))?; + let objects = objects.ok_or_else(|| M::Error::missing_field("objects"))?; + let type_ = type_.ok_or_else(|| M::Error::missing_field("type_"))?; + + let content = STIXBundleRequest { + id, + objects, + spec_version, + type_, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(STIXBundleRequestVisitor) + } +} diff --git a/src/datadogV2/model/model_stix_bundle_type.rs b/src/datadogV2/model/model_stix_bundle_type.rs new file mode 100644 index 000000000..0748adca7 --- /dev/null +++ b/src/datadogV2/model/model_stix_bundle_type.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXBundleType { + BUNDLE, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXBundleType { + fn to_string(&self) -> String { + match self { + Self::BUNDLE => String::from("bundle"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXBundleType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXBundleType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "bundle" => Self::BUNDLE, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/src/datadogV2/model/model_stix_content_encoding.rs b/src/datadogV2/model/model_stix_content_encoding.rs new file mode 100644 index 000000000..f75e968a4 --- /dev/null +++ b/src/datadogV2/model/model_stix_content_encoding.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXContentEncoding { + GZIP, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXContentEncoding { + fn to_string(&self) -> String { + match self { + Self::GZIP => String::from("gzip"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXContentEncoding { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXContentEncoding { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "gzip" => Self::GZIP, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/src/datadogV2/model/model_stix_indicator_object.rs b/src/datadogV2/model/model_stix_indicator_object.rs new file mode 100644 index 000000000..dc0a7a6ba --- /dev/null +++ b/src/datadogV2/model/model_stix_indicator_object.rs @@ -0,0 +1,356 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// A STIX 2.1 indicator object. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct STIXIndicatorObject { + /// The confidence in the correctness of the indicator, from 0 through 100. + #[serde(rename = "confidence")] + pub confidence: Option, + /// The time when the indicator was created. + #[serde(rename = "created")] + pub created: chrono::DateTime, + /// Optional external reference metadata preserved with the indicator but not interpreted during ingestion. + #[serde(rename = "external_references")] + pub external_references: Option>>, + /// The STIX indicator identifier. + #[serde(rename = "id")] + pub id: String, + /// The open vocabulary terms that categorize the indicator. + #[serde(rename = "indicator_types")] + pub indicator_types: Option>, + /// Optional kill chain metadata preserved with the indicator but not interpreted during ingestion. + #[serde(rename = "kill_chain_phases")] + pub kill_chain_phases: Option>>, + /// Labels associated with the indicator. + #[serde(rename = "labels")] + pub labels: Option>, + /// The time when the indicator was last modified. + #[serde(rename = "modified")] + pub modified: chrono::DateTime, + /// References to marking definition objects that apply to the indicator. + #[serde(rename = "object_marking_refs")] + pub object_marking_refs: Option>, + /// The STIX pattern that identifies the observable. + #[serde(rename = "pattern")] + pub pattern: String, + /// The supported STIX pattern language. + #[serde(rename = "pattern_type")] + pub pattern_type: crate::datadogV2::model::STIXPatternType, + /// Whether the indicator has been revoked. + #[serde(rename = "revoked")] + pub revoked: Option, + /// The supported STIX specification version. + #[serde(rename = "spec_version")] + pub spec_version: crate::datadogV2::model::STIXSpecVersion, + /// The STIX object type for an indicator. + #[serde(rename = "type")] + pub type_: crate::datadogV2::model::STIXIndicatorType, + /// The time from which the indicator is considered valid. + #[serde(rename = "valid_from")] + pub valid_from: chrono::DateTime, + /// The time until which the indicator is considered valid. + #[serde(rename = "valid_until")] + pub valid_until: Option>, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl STIXIndicatorObject { + pub fn new( + created: chrono::DateTime, + id: String, + modified: chrono::DateTime, + pattern: String, + pattern_type: crate::datadogV2::model::STIXPatternType, + spec_version: crate::datadogV2::model::STIXSpecVersion, + type_: crate::datadogV2::model::STIXIndicatorType, + valid_from: chrono::DateTime, + ) -> STIXIndicatorObject { + STIXIndicatorObject { + confidence: None, + created, + external_references: None, + id, + indicator_types: None, + kill_chain_phases: None, + labels: None, + modified, + object_marking_refs: None, + pattern, + pattern_type, + revoked: None, + spec_version, + type_, + valid_from, + valid_until: None, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn confidence(mut self, value: i32) -> Self { + self.confidence = Some(value); + self + } + + pub fn external_references( + mut self, + value: Vec>, + ) -> Self { + self.external_references = Some(value); + self + } + + pub fn indicator_types(mut self, value: Vec) -> Self { + self.indicator_types = Some(value); + self + } + + pub fn kill_chain_phases( + mut self, + value: Vec>, + ) -> Self { + self.kill_chain_phases = Some(value); + self + } + + pub fn labels(mut self, value: Vec) -> Self { + self.labels = Some(value); + self + } + + pub fn object_marking_refs(mut self, value: Vec) -> Self { + self.object_marking_refs = Some(value); + self + } + + pub fn revoked(mut self, value: bool) -> Self { + self.revoked = Some(value); + self + } + + pub fn valid_until(mut self, value: chrono::DateTime) -> Self { + self.valid_until = Some(value); + self + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for STIXIndicatorObject { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct STIXIndicatorObjectVisitor; + impl<'a> Visitor<'a> for STIXIndicatorObjectVisitor { + type Value = STIXIndicatorObject; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut confidence: Option = None; + let mut created: Option> = None; + let mut external_references: Option< + Vec>, + > = None; + let mut id: Option = None; + let mut indicator_types: Option> = None; + let mut kill_chain_phases: Option< + Vec>, + > = None; + let mut labels: Option> = None; + let mut modified: Option> = None; + let mut object_marking_refs: Option> = None; + let mut pattern: Option = None; + let mut pattern_type: Option = None; + let mut revoked: Option = None; + let mut spec_version: Option = None; + let mut type_: Option = None; + let mut valid_from: Option> = None; + let mut valid_until: Option> = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "confidence" => { + if v.is_null() { + continue; + } + confidence = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "created" => { + created = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "external_references" => { + if v.is_null() { + continue; + } + external_references = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "id" => { + id = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "indicator_types" => { + if v.is_null() { + continue; + } + indicator_types = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "kill_chain_phases" => { + if v.is_null() { + continue; + } + kill_chain_phases = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "labels" => { + if v.is_null() { + continue; + } + labels = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "modified" => { + modified = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "object_marking_refs" => { + if v.is_null() { + continue; + } + object_marking_refs = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "pattern" => { + pattern = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "pattern_type" => { + pattern_type = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _pattern_type) = pattern_type { + match _pattern_type { + crate::datadogV2::model::STIXPatternType::UnparsedObject( + _pattern_type, + ) => { + _unparsed = true; + } + _ => {} + } + } + } + "revoked" => { + if v.is_null() { + continue; + } + revoked = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "spec_version" => { + spec_version = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _spec_version) = spec_version { + match _spec_version { + crate::datadogV2::model::STIXSpecVersion::UnparsedObject( + _spec_version, + ) => { + _unparsed = true; + } + _ => {} + } + } + } + "type" => { + type_ = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _type_) = type_ { + match _type_ { + crate::datadogV2::model::STIXIndicatorType::UnparsedObject( + _type_, + ) => { + _unparsed = true; + } + _ => {} + } + } + } + "valid_from" => { + valid_from = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "valid_until" => { + if v.is_null() { + continue; + } + valid_until = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let created = created.ok_or_else(|| M::Error::missing_field("created"))?; + let id = id.ok_or_else(|| M::Error::missing_field("id"))?; + let modified = modified.ok_or_else(|| M::Error::missing_field("modified"))?; + let pattern = pattern.ok_or_else(|| M::Error::missing_field("pattern"))?; + let pattern_type = + pattern_type.ok_or_else(|| M::Error::missing_field("pattern_type"))?; + let spec_version = + spec_version.ok_or_else(|| M::Error::missing_field("spec_version"))?; + let type_ = type_.ok_or_else(|| M::Error::missing_field("type_"))?; + let valid_from = valid_from.ok_or_else(|| M::Error::missing_field("valid_from"))?; + + let content = STIXIndicatorObject { + confidence, + created, + external_references, + id, + indicator_types, + kill_chain_phases, + labels, + modified, + object_marking_refs, + pattern, + pattern_type, + revoked, + spec_version, + type_, + valid_from, + valid_until, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(STIXIndicatorObjectVisitor) + } +} diff --git a/src/datadogV2/model/model_stix_indicator_type.rs b/src/datadogV2/model/model_stix_indicator_type.rs new file mode 100644 index 000000000..90a2b3586 --- /dev/null +++ b/src/datadogV2/model/model_stix_indicator_type.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXIndicatorType { + INDICATOR, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXIndicatorType { + fn to_string(&self) -> String { + match self { + Self::INDICATOR => String::from("indicator"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXIndicatorType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXIndicatorType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "indicator" => Self::INDICATOR, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/src/datadogV2/model/model_stix_ingest_response.rs b/src/datadogV2/model/model_stix_ingest_response.rs new file mode 100644 index 000000000..3b77343fb --- /dev/null +++ b/src/datadogV2/model/model_stix_ingest_response.rs @@ -0,0 +1,92 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// The response from a completed STIX ingestion request. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct STIXIngestResponse { + /// The JSON:API resource describing the completed STIX ingestion request. + #[serde(rename = "data")] + pub data: crate::datadogV2::model::STIXIngestResponseData, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl STIXIngestResponse { + pub fn new(data: crate::datadogV2::model::STIXIngestResponseData) -> STIXIngestResponse { + STIXIngestResponse { + data, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for STIXIngestResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct STIXIngestResponseVisitor; + impl<'a> Visitor<'a> for STIXIngestResponseVisitor { + type Value = STIXIngestResponse; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut data: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "data" => { + data = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let data = data.ok_or_else(|| M::Error::missing_field("data"))?; + + let content = STIXIngestResponse { + data, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(STIXIngestResponseVisitor) + } +} diff --git a/src/datadogV2/model/model_stix_ingest_response_attributes.rs b/src/datadogV2/model/model_stix_ingest_response_attributes.rs new file mode 100644 index 000000000..76ff910d9 --- /dev/null +++ b/src/datadogV2/model/model_stix_ingest_response_attributes.rs @@ -0,0 +1,114 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// Counters describing the result of the STIX ingestion request. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct STIXIngestResponseAttributes { + /// The number of supported indicators added. + #[serde(rename = "added")] + pub added: i64, + /// The number of indicators with patterns that could not be parsed. + #[serde(rename = "invalid")] + pub invalid: i64, + /// The number of unsupported objects or patterns. + #[serde(rename = "unsupported")] + pub unsupported: i64, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl STIXIngestResponseAttributes { + pub fn new(added: i64, invalid: i64, unsupported: i64) -> STIXIngestResponseAttributes { + STIXIngestResponseAttributes { + added, + invalid, + unsupported, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for STIXIngestResponseAttributes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct STIXIngestResponseAttributesVisitor; + impl<'a> Visitor<'a> for STIXIngestResponseAttributesVisitor { + type Value = STIXIngestResponseAttributes; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut added: Option = None; + let mut invalid: Option = None; + let mut unsupported: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "added" => { + added = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "invalid" => { + invalid = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "unsupported" => { + unsupported = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let added = added.ok_or_else(|| M::Error::missing_field("added"))?; + let invalid = invalid.ok_or_else(|| M::Error::missing_field("invalid"))?; + let unsupported = + unsupported.ok_or_else(|| M::Error::missing_field("unsupported"))?; + + let content = STIXIngestResponseAttributes { + added, + invalid, + unsupported, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(STIXIngestResponseAttributesVisitor) + } +} diff --git a/src/datadogV2/model/model_stix_ingest_response_data.rs b/src/datadogV2/model/model_stix_ingest_response_data.rs new file mode 100644 index 000000000..b8f4699af --- /dev/null +++ b/src/datadogV2/model/model_stix_ingest_response_data.rs @@ -0,0 +1,125 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// The JSON:API resource describing the completed STIX ingestion request. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct STIXIngestResponseData { + /// Counters describing the result of the STIX ingestion request. + #[serde(rename = "attributes")] + pub attributes: crate::datadogV2::model::STIXIngestResponseAttributes, + /// The normalized vendor identifier. + #[serde(rename = "id")] + pub id: String, + /// The STIX ingestion resource type. + #[serde(rename = "type")] + pub type_: crate::datadogV2::model::STIXIngestResponseType, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl STIXIngestResponseData { + pub fn new( + attributes: crate::datadogV2::model::STIXIngestResponseAttributes, + id: String, + type_: crate::datadogV2::model::STIXIngestResponseType, + ) -> STIXIngestResponseData { + STIXIngestResponseData { + attributes, + id, + type_, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl<'de> Deserialize<'de> for STIXIngestResponseData { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct STIXIngestResponseDataVisitor; + impl<'a> Visitor<'a> for STIXIngestResponseDataVisitor { + type Value = STIXIngestResponseData; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut attributes: Option = + None; + let mut id: Option = None; + let mut type_: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "attributes" => { + attributes = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "id" => { + id = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "type" => { + type_ = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + if let Some(ref _type_) = type_ { + match _type_ { + crate::datadogV2::model::STIXIngestResponseType::UnparsedObject(_type_) => { + _unparsed = true; + }, + _ => {} + } + } + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + let attributes = attributes.ok_or_else(|| M::Error::missing_field("attributes"))?; + let id = id.ok_or_else(|| M::Error::missing_field("id"))?; + let type_ = type_.ok_or_else(|| M::Error::missing_field("type_"))?; + + let content = STIXIngestResponseData { + attributes, + id, + type_, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(STIXIngestResponseDataVisitor) + } +} diff --git a/src/datadogV2/model/model_stix_ingest_response_type.rs b/src/datadogV2/model/model_stix_ingest_response_type.rs new file mode 100644 index 000000000..838e72a9a --- /dev/null +++ b/src/datadogV2/model/model_stix_ingest_response_type.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXIngestResponseType { + THREAT_INTEL_STIX_INGEST, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXIngestResponseType { + fn to_string(&self) -> String { + match self { + Self::THREAT_INTEL_STIX_INGEST => String::from("threat-intel-stix-ingest"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXIngestResponseType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXIngestResponseType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "threat-intel-stix-ingest" => Self::THREAT_INTEL_STIX_INGEST, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/src/datadogV2/model/model_stix_pattern_type.rs b/src/datadogV2/model/model_stix_pattern_type.rs new file mode 100644 index 000000000..481cf349b --- /dev/null +++ b/src/datadogV2/model/model_stix_pattern_type.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXPatternType { + STIX, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXPatternType { + fn to_string(&self) -> String { + match self { + Self::STIX => String::from("stix"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXPatternType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXPatternType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "stix" => Self::STIX, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/src/datadogV2/model/model_stix_spec_version.rs b/src/datadogV2/model/model_stix_spec_version.rs new file mode 100644 index 000000000..c2debab84 --- /dev/null +++ b/src/datadogV2/model/model_stix_spec_version.rs @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum STIXSpecVersion { + VERSION_2_1, + UnparsedObject(crate::datadog::UnparsedObject), +} + +impl ToString for STIXSpecVersion { + fn to_string(&self) -> String { + match self { + Self::VERSION_2_1 => String::from("2.1"), + Self::UnparsedObject(v) => v.value.to_string(), + } + } +} + +impl Serialize for STIXSpecVersion { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::UnparsedObject(v) => v.serialize(serializer), + _ => serializer.serialize_str(self.to_string().as_str()), + } + } +} + +impl<'de> Deserialize<'de> for STIXSpecVersion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(match s.as_str() { + "2.1" => Self::VERSION_2_1, + _ => Self::UnparsedObject(crate::datadog::UnparsedObject { + value: serde_json::Value::String(s.into()), + }), + }) + } +} diff --git a/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.frozen b/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.frozen new file mode 100644 index 000000000..62819102a --- /dev/null +++ b/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.frozen @@ -0,0 +1 @@ +2026-07-24T13:40:34.989Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.json b/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.json new file mode 100644 index 000000000..c6bb21c9c --- /dev/null +++ b/tests/scenarios/cassettes/v2/threat_intelligence/Ingest-STIX-threat-intelligence-returns-OK-response.json @@ -0,0 +1,39 @@ +{ + "http_interactions": [ + { + "request": { + "body": { + "string": "{\"id\":\"bundle--44444444-4444-4444-8444-444444444444\",\"objects\":[{\"created\":\"2026-07-22T12:00:00Z\",\"id\":\"indicator--55555555-5555-4555-8555-555555555555\",\"modified\":\"2026-07-22T12:00:00Z\",\"pattern\":\"[ipv4-addr:value = '198.51.100.42']\",\"pattern_type\":\"stix\",\"spec_version\":\"2.1\",\"type\":\"indicator\",\"valid_from\":\"2026-07-22T12:00:00Z\"}],\"spec_version\":\"2.1\",\"type\":\"bundle\"}", + "encoding": null + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/security/threat-intel/stix" + }, + "response": { + "body": { + "string": "{\"data\":{\"id\":\"acme_inc\",\"type\":\"threat-intel-stix-ingest\",\"attributes\":{\"added\":1,\"invalid\":0,\"unsupported\":0}}}", + "encoding": null + }, + "headers": { + "Content-Type": [ + "application/vnd.api+json" + ] + }, + "status": { + "code": 200, + "message": "OK" + } + }, + "recorded_at": "Fri, 24 Jul 2026 13:40:34 GMT" + } + ], + "recorded_with": "VCR 6.0.0" +} \ No newline at end of file diff --git a/tests/scenarios/features/v2/threat_intelligence.feature b/tests/scenarios/features/v2/threat_intelligence.feature new file mode 100644 index 000000000..22385a31f --- /dev/null +++ b/tests/scenarios/features/v2/threat_intelligence.feature @@ -0,0 +1,37 @@ +@endpoint(threat-intelligence) @endpoint(threat-intelligence-v2) +Feature: Threat Intelligence + Ingest and manage threat intelligence data for security enrichment and + investigation. + + Background: + Given a valid "apiKeyAuth" key in the system + And a valid "appKeyAuth" key in the system + And an instance of "ThreatIntelligence" API + And operation "IngestStixThreatIntel" enabled + And new "IngestStixThreatIntel" request + + @generated @skip @team:DataDog/cloud-siem + Scenario: Ingest STIX threat intelligence returns "Bad Request" response + Given body with value {"id": "bundle--11111111-1111-4111-8111-111111111111", "objects": [{"created": "2026-07-22T12:00:00Z", "id": "indicator--22222222-2222-4222-8222-222222222222", "modified": "2026-07-22T12:00:00Z", "pattern": "[ipv4-addr:value = '198.51.100.42']", "pattern_type": "stix", "spec_version": "2.1", "type": "indicator", "valid_from": "2026-07-22T12:00:00Z"}], "spec_version": "2.1", "type": "bundle"} + And request contains "ti_vendor" parameter from "REPLACE.ME" + When the request is sent + Then the response status is 400 Bad Request + + @replay-only @team:DataDog/cloud-siem + Scenario: Ingest STIX threat intelligence returns "OK" response + Given request contains "ti_vendor" parameter with value "Acme-Inc" + And body with value {"id": "bundle--44444444-4444-4444-8444-444444444444", "objects": [{"created": "2026-07-22T12:00:00Z", "id": "indicator--55555555-5555-4555-8555-555555555555", "modified": "2026-07-22T12:00:00Z", "pattern": "[ipv4-addr:value = '198.51.100.42']", "pattern_type": "stix", "spec_version": "2.1", "type": "indicator", "valid_from": "2026-07-22T12:00:00Z"}], "spec_version": "2.1", "type": "bundle"} + When the request is sent + Then the response status is 200 OK + And the response "data.type" is equal to "threat-intel-stix-ingest" + And the response "data.id" is equal to "acme_inc" + And the response "data.attributes.added" is equal to 1 + And the response "data.attributes.unsupported" is equal to 0 + And the response "data.attributes.invalid" is equal to 0 + + @skip @team:DataDog/cloud-siem + Scenario: Ingest STIX threat intelligence returns "Payload Too Large" response + Given request contains "ti_vendor" parameter with value "Acme-Inc" + And body with value {"id": "bundle--66666666-6666-4666-8666-666666666666", "objects": [{"created": "2026-07-22T12:00:00Z", "id": "indicator--77777777-7777-4777-8777-777777777777", "modified": "2026-07-22T12:00:00Z", "pattern": "[ipv4-addr:value = '198.51.100.42']", "pattern_type": "stix", "spec_version": "2.1", "type": "indicator", "valid_from": "2026-07-22T12:00:00Z"}], "spec_version": "2.1", "type": "bundle"} + When the request is sent + Then the response status is 413 Payload Too Large diff --git a/tests/scenarios/features/v2/undo.json b/tests/scenarios/features/v2/undo.json index d93991ac7..eccfda602 100644 --- a/tests/scenarios/features/v2/undo.json +++ b/tests/scenarios/features/v2/undo.json @@ -7549,6 +7549,12 @@ "type": "idempotent" } }, + "IngestStixThreatIntel": { + "tag": "Threat Intelligence", + "undo": { + "type": "safe" + } + }, "ListVulnerabilities": { "tag": "Security Monitoring", "undo": { diff --git a/tests/scenarios/function_mappings.rs b/tests/scenarios/function_mappings.rs index 2fd7318e5..19a412aea 100644 --- a/tests/scenarios/function_mappings.rs +++ b/tests/scenarios/function_mappings.rs @@ -210,6 +210,8 @@ pub struct ApiInstances { pub v2_api_scorecards: Option, pub v2_api_seats: Option, pub v2_api_entity_risk_scores: Option, + pub v2_api_threat_intelligence: + Option, pub v2_api_sensitive_data_scanner: Option, pub v2_api_service_accounts: Option, @@ -1363,6 +1365,14 @@ pub fn initialize_api_instance(world: &mut DatadogWorld, api: String) { ), ); } + "ThreatIntelligence" => { + world.api_instances.v2_api_threat_intelligence = Some( + datadogV2::api_threat_intelligence::ThreatIntelligenceAPI::with_client_and_config( + world.config.clone(), + world.http_client.as_ref().unwrap().clone(), + ), + ); + } "SensitiveDataScanner" => { world.api_instances.v2_api_sensitive_data_scanner = Some(datadogV2::api_sensitive_data_scanner::SensitiveDataScannerAPI::with_client_and_config( world.config.clone(), @@ -7083,6 +7093,10 @@ pub fn collect_function_calls(world: &mut DatadogWorld) { "v2.GetEntityRiskScore".into(), test_v2_get_entity_risk_score, ); + world.function_mappings.insert( + "v2.IngestStixThreatIntel".into(), + test_v2_ingest_stix_threat_intel, + ); world .function_mappings .insert("v2.ListScanningGroups".into(), test_v2_list_scanning_groups); @@ -55963,6 +55977,42 @@ fn test_v2_get_entity_risk_score(world: &mut DatadogWorld, _parameters: &HashMap world.response.code = response.status.as_u16(); } +fn test_v2_ingest_stix_threat_intel( + world: &mut DatadogWorld, + _parameters: &HashMap, +) { + let api = world + .api_instances + .v2_api_threat_intelligence + .as_ref() + .expect("api instance not found"); + let ti_vendor = serde_json::from_value(_parameters.get("ti_vendor").unwrap().clone()).unwrap(); + let body = serde_json::from_value(_parameters.get("body").unwrap().clone()).unwrap(); + let content_encoding = _parameters + .get("Content-Encoding") + .and_then(|param| Some(serde_json::from_value(param.clone()).unwrap())); + let mut params = + datadogV2::api_threat_intelligence::IngestStixThreatIntelOptionalParams::default(); + params.content_encoding = content_encoding; + let response = + match block_on(api.ingest_stix_threat_intel_with_http_info(ti_vendor, body, params)) { + Ok(response) => response, + Err(error) => { + return match error { + Error::ResponseError(e) => { + world.response.code = e.status.as_u16(); + if let Some(entity) = e.entity { + world.response.object = serde_json::to_value(entity).unwrap(); + } + } + _ => panic!("error parsing response: {error}"), + }; + } + }; + world.response.object = serde_json::to_value(response.entity).unwrap(); + world.response.code = response.status.as_u16(); +} + fn test_v2_list_scanning_groups(world: &mut DatadogWorld, _parameters: &HashMap) { let api = world .api_instances