HashicorpTerraform
Hashicorp Terraform provide infrastructure automation to provision and manage resources in any cloud or data center with Terraform.
IT Services · HashiCorp Terraform
Details
| ID | HashicorpTerraform |
|---|---|
| Provider | IBM |
| Category | IT Services |
| From Version | 6.10.0 |
| Docker Image | demisto/auth-utils:1.0.0.10133006 |
| Supported Modules | Agentix XSIAM |
README
Hashicorp Terraform provide infrastructure automation to provision and manage resources in any cloud or data center with Terraform.
This integration was integrated and tested with version v1.4.4 of HashicorpTerraform.
Configure HashiCorp Terraform in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| API Token | The API Key to use for connection. | True |
| Default Organization Name | There is an option to override with a command input parameter. If not provided, policy commands should require the organization name. | False |
| Default Workspace ID | There is an option to override with an input parameter. If not provided, some commands should require the workspace ID. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch events | False | |
| Maximum Number of Audit Events Per Fetch | Default is 10000. | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
terraform-runs-list
List runs in a workspace.
Base Command
terraform-runs-list
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| workspace_id | The workspace ID to list runs for. | Optional |
| run_id | The run ID to get a specific run. | Optional |
| filter_status | The run status to filter by. Possible values are: pending, fetching, fetching_completed, pre_plan_running, pre_plan_completed, queuing, plan_queued, planning, planned, cost_estimating, cost_estimated, policy_checking, policy_override, policy_soft_failed, policy_checked, confirmed, post_plan_running, post_plan_completed, planned_and_finished, planned_and_saved, apply_queued, applying, applied, discarded, errored, canceled, force_canceled. | Optional |
| page_number | The page number of the results to return. Default is 1. | Optional |
| page_size | The number of results to return per page. Default is 20, maximum is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Terraform.Run.data.id | String | The run ID. |
| Terraform.Run.data.attributes.status | String | The run status. |
| Terraform.Run.data.relationships.plan.data.id | String | The plan ID. |
| Terraform.Run.data.attributes.status-timestamps.planned-at | Date | The datetime the plan was planned. |
| Terraform.Run.data.type | String | THe run type. |
| Terraform.Run.data.attributes.actions.is-cancelable | Boolean | Flag indicating whether the Terraform run can be canceled. |
| Terraform.Run.data.attributes.actions.is-confirmable | Boolean | Flag indicating whether the Terraform run can be confirmed. |
| Terraform.Run.data.attributes.actions.is-discardable | Boolean | Flag indicating whether the Terraform run can be discarded. |
| Terraform.Run.data.attributes.actions.is-force-cancelable | Boolean | Flag indicating whether the Terraform run can be force-canceled. |
| Terraform.Run.data.attributes.canceled-at | Unknown | Timestamp indicating when the Terraform run was canceled. |
| Terraform.Run.data.attributes.created-at | Date | Timestamp indicating when the Terraform run was created. |
| Terraform.Run.data.attributes.has-changes | Boolean | Flag indicating whether there are changes in the Terraform run. |
| Terraform.Run.data.attributes.auto-apply | Boolean | Flag indicating whether auto-apply is enabled for the Terraform run. |
| Terraform.Run.data.attributes.allow-empty-apply | Boolean | Flag indicating whether empty apply is allowed for the Terraform run. |
| Terraform.Run.data.attributes.allow-config-generation | Boolean | Flag indicating whether configuration generation is allowed for the Terraform run. |
| Terraform.Run.data.attributes.is-destroy | Boolean | Flag indicating whether the Terraform run is a destroy operation. |
| Terraform.Run.data.attributes.message | String | Text message associated with the Terraform run. |
| Terraform.Run.data.attributes.plan-only | Boolean | Flag indicating whether the Terraform run is for planning only. |
| Terraform.Run.data.attributes.source | String | Source of the Terraform run. |
| Terraform.Run.data.attributes.status-timestamps.plan-queueable-at | Date | Timestamp indicating when the Terraform run is queueable in the plan stage. |
| Terraform.Run.data.attributes.trigger-reason | String | Reason for triggering the Terraform run. |
| Terraform.Run.data.attributes.target-addrs | Unknown | Target addresses associated with the Terraform run. |
| Terraform.Run.data.attributes.permissions.can-apply | Boolean | Flag indicating whether the user has permission to apply changes. |
| Terraform.Run.data.attributes.permissions.can-cancel | Boolean | Flag indicating whether the user has permission to cancel the Terraform run. |
| Terraform.Run.data.attributes.permissions.can-comment | Boolean | Flag indicating whether the user has permission to add comments. |
| Terraform.Run.data.attributes.permissions.can-discard | Boolean | Flag indicating whether the user has permission to discard the Terraform run. |
| Terraform.Run.data.attributes.permissions.can-force-execute | Boolean | Flag indicating whether the user has permission to force execute the Terraform run. |
| Terraform.Run.data.attributes.permissions.can-force-cancel | Boolean | Flag indicating whether the user has permission to force cancel the Terraform run. |
| Terraform.Run.data.attributes.permissions.can-override-policy-check | Boolean | Flag indicating whether the user has permission to override policy checks. |
| Terraform.Run.data.attributes.refresh | Boolean | Flag indicating whether the Terraform run should perform a refresh. |
| Terraform.Run.data.attributes.refresh-only | Boolean | Flag indicating whether the Terraform run is for refresh only. |
| Terraform.Run.data.attributes.replace-addrs | Unknown | Replacement addresses associated with the Terraform run. |
| Terraform.Run.data.attributes.save-plan | Boolean | Flag indicating whether the Terraform run plan should be saved. |
| Terraform.Run.data.attributes.variables | Unknown | Variables associated with the Terraform run. |
| Terraform.Run.data.relationships.apply.data.id | String | The apply ID of the run. |
| Terraform.Run.data.relationships.comments | String | Relationship information for comments associated with the Terraform run. |
| Terraform.Run.data.relationships.configuration-version | String | Relationship information for the Terraform configuration version associated with the run. |
| Terraform.Run.data.relationships.cost-estimate | String | Relationship information for cost estimates associated with the Terraform run. |
| Terraform.Run.data.relationships.created-by | String | Relationship information for the user who created the Terraform run. |
| Terraform.Run.data.relationships.input-state-version | String | Relationship information for the input state version associated with the Terraform run. |
| Terraform.Run.data.relationships.run-events | String | Relationship information for events associated with the Terraform run. |
| Terraform.Run.data.relationships.policy-checks | String | Relationship information for policy checks associated with the Terraform run. |
| Terraform.Run.data.relationships.workspace | String | Relationship information for the Terraform workspace associated with the run. |
| Terraform.Run.data.relationships.workspace-run-alerts | String | Relationship information for alerts associated with the Terraform workspace run. |
| Terraform.Run.data.links.self | String | Link to the Terraform run data. |
Command example
#### Context Example
```json
{
"Terraform": {
"Run": {
"data": [
{
"attributes": {
"actions": {
"is-cancelable": false,
"is-confirmable": true,
"is-discardable": true,
"is-force-cancelable": false
},
"allow-config-generation": false,
"allow-empty-apply": false,
"auto-apply": false,
"canceled-at": null,
"created-at": "2023-12-17T10:23:43.258Z",
"has-changes": true,
"is-destroy": false,
"message": "Triggered via UI",
"permissions": {
"can-apply": true,
"can-cancel": true,
"can-comment": true,
"can-discard": true,
"can-force-cancel": true,
"can-force-execute": true,
"can-override-policy-check": true
},
"plan-only": false,
"refresh": true,
"refresh-only": false,
"replace-addrs": [
"fakewebservices_load_balancer.primary_lb"
],
"save-plan": false,
"source": "tfe-ui",
"status": "planned",
"status-timestamps": {
"plan-queueable-at": "2023-12-17T10:23:43+00:00",
"plan-queued-at": "2023-12-17T10:23:43+00:00",
"planned-at": "2023-12-17T10:23:52+00:00",
"planning-at": "2023-12-17T10:23:48+00:00",
"queuing-at": "2023-12-17T10:23:43+00:00"
},
"target-addrs": null,
"terraform-version": "1.4.4",
"trigger-reason": "manual",
"variables": []
},
"id": "run-8wpCneWr4TLSzfat",
"links": {
"self": "/api/v2/runs/run-8wpCneWr4TLSzfat"
},
"relationships": {
"apply": {
"data": {
"id": "apply-uEYtCmrtg5MvjgTr",
"type": "applies"
},
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/apply"
}
},
"comments": {
"data": [],
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/comments"
}
},
"configuration-version": {
"data": {
"id": "cv-YDcZaBNiRbrdy1w1",
"type": "configuration-versions"
},
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/configuration-version"
}
},
"created-by": {
"data": {
"id": "user-LR5kedWrdZXBWF71",
"type": "users"
},
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/created-by"
}
},
"plan": {
"data": {
"id": "plan-T7zpGYFEioRfWEAq",
"type": "plans"
},
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/plan"
}
},
"policy-checks": {
"data": [],
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/policy-checks"
}
},
"run-events": {
"data": [
{
"id": "re-ga2h6eu41RqrmZRn",
"type": "run-events"
},
{
"id": "re-ciqzHkW3bDooRzcn",
"type": "run-events"
},
{
"id": "re-ENvbqnmE72YFj7Wq",
"type": "run-events"
},
{
"id": "re-xkz1fSTKM25GxkMk",
"type": "run-events"
}
],
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/run-events"
}
},
"task-stages": {
"data": [],
"links": {
"related": "/api/v2/runs/run-8wpCneWr4TLSzfat/task-stages"
}
},
"workspace": {
"data": {
"id": "ws-ZTbNWsfXHRWRVNmE",
"type": "workspaces"
}
}
},
"type": "runs"
}
],
"links": {
"first": "https://app.terraform.io/api/v2/workspaces/ws-ZTbNWsfXHRWRVNmE/runs?page%5Bnumber%5D=1&page%5Bsize%5D=20",
"last": "https://app.terraform.io/api/v2/workspaces/ws-ZTbNWsfXHRWRVNmE/runs?page%5Bnumber%5D=1&page%5Bsize%5D=20",
"next": null,
"prev": null,
"self": "https://app.terraform.io/api/v2/workspaces/ws-ZTbNWsfXHRWRVNmE/runs?page%5Bnumber%5D=1&page%5Bsize%5D=20"
},
"meta": {
"pagination": {
"current-page": 1,
"next-page": null,
"page-size": 20,
"prev-page": null,
"total-count": 9,
"total-pages": 1
},
"status-counts": {
"applied": 1,
"apply-queued": 0,
"applying": 0,
"assessed": 0,
"assessing": 0,
"canceled": 2,
"confirmed": 0,
"cost-estimated": 0,
"cost-estimating": 0,
"discarded": 4,
"errored": 0,
"fetching": 0,
"fetching-completed": 0,
"pending": 0,
"plan-queued": 0,
"planned": 1,
"planned-and-finished": 4,
"planned-and-saved": 0,
"planning": 0,
"policy-checked": 0,
"policy-checking": 0,
"policy-override": 0,
"policy-soft-failed": 0,
"post-apply-completed": 0,
"post-apply-running": 0,
"post-plan-awaiting-decision": 0,
"post-plan-completed": 0,
"post-plan-running": 0,
"pre-apply-awaiting-decision": 0,
"pre-apply-completed": 0,
"pre-apply-running": 0,
"pre-plan-awaiting-decision": 0,
"pre-plan-completed": 0,
"pre-plan-running": 0,
"queuing": 0,
"queuing-apply": 0,
"total": 12
}
}
}
}
}
Human Readable Output
Terraform Runs
Plan id Planned at Run id Status plan-T7zpGYFEioRfWEAq 2023-12-17T10:23:52+00:00 run-8wpCneWr4TLSzfat planned plan-1JUTBdedobs1Absf 2023-12-11T11:35:35+00:00 run-kMNQfAmoDr1k8eaT discarded plan-21bfTFiDJ6Rz1VTZ run-jb2j5r3gBievUPfR canceled plan-twBdAcLwiGwuE7kt 2023-12-11T11:29:38+00:00 run-g7ihSa71hCV9yZt7 discarded plan-JEgrv5aBeNUDDRaA 2023-12-11T11:12:04+00:00 run-yCYvcx1ZEmmKGXnB discarded plan-kJLmtoaywxkXM54P 2023-12-11T09:11:48+00:00 run-akCRvcJ6L5cQtAhc discarded plan-ZunKDF28KpCyiZAn 2023-12-10T07:10:08+00:00 run-rpSjBkbhiKAfMuwX planned_and_finished plan-V4fvpvCzGQrsZikD 2023-11-30T09:21:42+00:00 run-Q2kS54r6pJjdyYfk planned_and_finished plan-ZYYZD69ESo16jENX 2023-10-25T10:33:11+00:00 run-wBdFQ6egn91GGRne applied
terraform-run-action
Perform an action on a Terraform run. The available actions are: apply, cancel, discard, force-cancel, force-execute.
Base Command
terraform-run-action
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | The Terraform run ID to execute the action on. | Required |
| action | The action to execute on the Terraform run. Possible values are: apply, cancel, discard, force-cancel, force-execute. | Required |
| comment | An optional comment to associate with the action. Not available for the action “force-execute”. | Optional |
Context Output
There is no context output for this command.
Command example
!terraform-run-action run_id=run-8wpCneWr4TLSzfat action="discard" comment="test comment"
Human Readable Output
Successfully queued an discard request for run id run-8wpCneWr4TLSzfat
terraform-plan-get
Get the plan JSON file or the plan meta data.
Base Command
terraform-plan-get
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| plan_id | The ID of the Terraform plan to retrieve. | Required |
| json_output | Whether to return the plan as a JSON fileResult. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Terraform.Plan.id | String | The plan ID. |
| Terraform.Plan.attributes.status | String | The plan status. |
| Terraform.Plan.type | String | Type of the Terraform plan data. |
| Terraform.Plan.attributes.has-changes | Boolean | Flag indicating whether the Terraform plan has changes. |
| Terraform.Plan.attributes.status-timestamps.started-at | Date | Timestamp indicating when the Terraform plan started. |
| Terraform.Plan.attributes.status-timestamps.finished-at | Date | Timestamp indicating when the Terraform plan finished. |
| Terraform.Plan.attributes.status-timestamps.agent-queued-at | Date | Timestamp indicating when the Terraform plan was queued for an agent. |
| Terraform.Plan.attributes.log-read-url | String | URL for reading the Terraform plan log. |
| Terraform.Plan.attributes.resource-additions | Number | Number of resource additions in the Terraform plan. |
| Terraform.Plan.attributes.resource-changes | Number | Number of resource changes in the Terraform plan. |
| Terraform.Plan.attributes.resource-destructions | Number | Number of resource destructions in the Terraform plan. |
| Terraform.Plan.attributes.resource-imports | Number | Number of resource imports in the Terraform plan. |
| Terraform.Plan.attributes.structured-run-output-enabled | Boolean | Flag indicating whether structured run output is enabled in the Terraform plan. |
| Terraform.Plan.attributes.generated-configuration | Boolean | Flag indicating whether the Terraform plan has generated configuration. |
| Terraform.Plan.attributes.actions.is-exportable | Boolean | Flag indicating whether the Terraform plan is exportable. |
| Terraform.Plan.attributes.execution-details.mode | String | Execution mode details for the Terraform plan. |
| Terraform.Plan.attributes.permissions.can-export | Boolean | Flag indicating whether the user has permission to export the Terraform plan. |
| Terraform.Plan.relationships.state-versions.data | Unknown | Relationship information for state versions associated with the Terraform plan. |
| Terraform.Plan.relationships.exports.data | Unknown | Relationship information for exports associated with the Terraform plan. |
| Terraform.Plan.links.self | String | Link to the Terraform plan data. |
| Terraform.Plan.links.json-output | String | Link to the JSON output of the Terraform plan. |
| Terraform.Plan.links.json-output-redacted | String | Link to the redacted JSON output of the Terraform plan. |
| Terraform.Plan.links.json-schema | String | Link to the JSON schema of the Terraform plan. |
Command example
!terraform-plan-get plan_id=plan-V4fvpvCzGQrsZikD
Context Example
{
"Terraform": {
"Plan": {
"attributes": {
"actions": {
"is-exportable": true
},
"execution-details": {
"mode": "remote"
},
"generated-configuration": false,
"has-changes": false,
"log-read-url": "url",
"permissions": {
"can-export": true
},
"resource-additions": 0,
"resource-changes": 0,
"resource-destructions": 0,
"resource-imports": 0,
"status": "finished",
"status-timestamps": {
"agent-queued-at": "2023-11-30T09:21:33+00:00",
"finished-at": "2023-11-30T09:21:41+00:00",
"started-at": "2023-11-30T09:21:37+00:00"
},
"structured-run-output-enabled": true
},
"id": "plan-V4fvpvCzGQrsZikD",
"links": {
"json-output": "/api/v2/plans/plan-V4fvpvCzGQrsZikD/json-output",
"json-output-redacted": "/api/v2/plans/plan-V4fvpvCzGQrsZikD/json-output-redacted",
"json-schema": "/api/v2/plans/plan-V4fvpvCzGQrsZikD/json-schema",
"self": "/api/v2/plans/plan-V4fvpvCzGQrsZikD"
},
"relationships": {
"exports": {
"data": []
},
"state-versions": {
"data": []
}
},
"type": "plans"
}
}
}
Human Readable Output
Terraform Plan
Agent Queued at Plan id Status 2023-11-30T09:21:33+00:00 plan-V4fvpvCzGQrsZikD finished
Command example
!terraform-plan-get plan_id=plan-V4fvpvCzGQrsZikD json_output="true"
Context Example
{
"InfoFile": {
"EntryID": "375@03d8b507-a516-4959-8133-979b2d80a807",
"Extension": "json",
"Info": "application/json",
"Name": "plan-V4fvpvCzGQrsZikD.json",
"Size": 3686,
"Type": "JSON data"
}
}
Human Readable Output
terraform-policies-list
List the policies for an organization or get a specific policy.
Base Command
terraform-policies-list
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| organization_name | The name of the organization. | Optional |
| policy_kind | If specified, restricts results to those with the matching policy kind value. Possible values are: sentinel, opa. | Optional |
| policy_name | If specified, search the organization’s policies by name. | Optional |
| policy_id | If specified, get the specific policy. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Terraform.Policy.id | String | The policy ID. |
| Terraform.Policy.type | String | The policy type. |
| Terraform.Policy.attributes.name | String | Name of the Terraform policy. |
| Terraform.Policy.attributes.description | Unknown | Description of the Terraform policy. |
| Terraform.Policy.attributes.enforce.path | String | Path for enforcing the Terraform policy. |
| Terraform.Policy.attributes.enforce.mode | String | Enforcement mode for the Terraform policy. |
| Terraform.Policy.attributes.policy-set-count | Number | Count of policy sets associated with the Terraform policy. |
| Terraform.Policy.attributes.updated-at | Date | Timestamp indicating when the Terraform policy was last updated. |
| Terraform.Policy.attributes.kind | String | Kind of the Terraform policy. |
| Terraform.Policy.attributes.enforcement-level | String | Enforcement level for the Terraform policy. |
| Terraform.Policy.relationships.organization.data.id | String | Unique identifier for the organization associated with the Terraform policy. |
| Terraform.Policy.relationships.organization.data.type | String | Type of the organization associated with the Terraform policy. |
| Terraform.Policy.relationships.policy-sets.data.id | String | The IDs of the policy sets that contain this policy. |
| Terraform.Policy.relationships.policy-sets.data.type | String | Type of the policy sets associated with the Terraform policy. |
| Terraform.Policy.links.self | String | Link to the Terraform policy data. |
| Terraform.Policy.links.upload | String | Link for uploading the Terraform policy. |
| Terraform.Policy.links.download | String | Link for downloading the Terraform policy. |
| Terraform.Policy.links.self | String | Link to the Terraform policy. |
| Terraform.Policy.links.first | String | Link to the first page of Terraform policies. |
| Terraform.Policy.links.prev | Unknown | Link to the previous page of Terraform policies. |
| Terraform.Policy.links.next | Unknown | Link to the next page of Terraform policies. |
| Terraform.Policy.links.last | String | Link to the last page of Terraform policies. |
| Terraform.Policy.meta.pagination.current-page | Number | Current page number in the pagination of Terraform policies. |
| Terraform.Policy.meta.pagination.page-size | Number | Number of policies displayed per page in pagination. |
| Terraform.Policy.meta.pagination.prev-page | Unknown | Previous page number in the pagination of Terraform policies. |
| Terraform.Policy.meta.pagination.next-page | Unknown | Next page number in the pagination of Terraform policies. |
| Terraform.Policy.meta.pagination.total-pages | Number | Total number of pages in the pagination of Terraform policies. |
| Terraform.Policy.meta.pagination.total-count | Number | Total count of Terraform policies. |
Command example
#### Context Example
```json
{
"Terraform": {
"Policy": {
"attributes": {
"description": null,
"enforce": [
{
"mode": "hard-mandatory",
"path": "nat-policies.sentinel"
}
],
"enforcement-level": "hard-mandatory",
"kind": "sentinel",
"name": "nat-policies",
"policy-set-count": 1,
"updated-at": "2023-11-14T18:12:36.702Z"
},
"id": "pol-ycCqXorxsFjaH5aK",
"links": {
"download": "/api/v2/policies/pol-ycCqXorxsFjaH5aK/download",
"self": "/api/v2/policies/pol-ycCqXorxsFjaH5aK",
"upload": "/api/v2/policies/pol-ycCqXorxsFjaH5aK/upload"
},
"relationships": {
"organization": {
"data": {
"id": "example-org-40dc3b",
"type": "organizations"
}
},
"policy-sets": {
"data": [
{
"id": "polset-hc2bvqDW8YRgHEt8",
"type": "policy-sets"
}
]
}
},
"type": "policies"
}
}
}
Human Readable Output
Terraform Policies
Kind Organization id Policy Set ids Policy id Policy name sentinel example-org-40dc3b polset-hc2bvqDW8YRgHEt8 pol-ycCqXorxsFjaH5aK nat-policies
terraform-policy-set-list
List the policy sets for an organization or get a specific policy set.
Base Command
terraform-policy-set-list
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| organization_name | The name of the organization. | Optional |
| policy_set_id | If specified, get the specific policy set. | Optional |
| versioned | Allows filtering policy sets based on whether they are versioned, or use individual policy relationships. A true value returns versioned sets, and a false value returns sets with individual policy relationships. If omitted, all policy sets are returned. Possible values are: true, false. | Optional |
| policy_set_kind | If specified, restricts results to those with the matching policy kind value. Possible values are: sentinel, opa. | Optional |
| include | Enables you to include related resource data. Value must be a comma-separated list containing one or more projects, workspaces, workspace-exclusions, policies, newest_version, or current_version. | Optional |
| policy_set_name | Allows searching the organization’s policy sets by name. | Optional |
| page_number | The page number of the results to return. Default is 1. | Optional |
| page_size | The number of results to return per page. Default is 20, maximum is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Terraform.PolicySet.id | String | The policy set ID. |
| Terraform.PolicySet.type | String | The policy set type. |
| Terraform.PolicySet.attributes.name | String | Name of the Terraform policy set. |
| Terraform.PolicySet.attributes.description | Unknown | Description of the Terraform policy set. |
| Terraform.PolicySet.attributes.global | Boolean | Flag indicating whether the Terraform policy set is global. |
| Terraform.PolicySet.attributes.workspace-count | Number | Number of workspaces associated with the Terraform policy set. |
| Terraform.PolicySet.attributes.project-count | Number | Number of projects associated with the Terraform policy set. |
| Terraform.PolicySet.attributes.created-at | Date | Timestamp indicating when the Terraform policy set was created. |
| Terraform.PolicySet.attributes.updated-at | Date | Timestamp indicating when the Terraform policy set was last updated. |
| Terraform.PolicySet.attributes.kind | String | Kind of the Terraform policy set. |
| Terraform.PolicySet.attributes.agent-enabled | Boolean | Flag indicating whether the Terraform policy set has agents enabled. |
| Terraform.PolicySet.attributes.policy-count | Number | Number of policies associated with the Terraform policy set. |
| Terraform.PolicySet.attributes.versioned | Boolean | Flag indicating whether the Terraform policy set is versioned. |
| Terraform.PolicySet.relationships.organization.data.id | String | ID of the organization associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.organization.data.type | String | Type of the organization associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.policies.data.id | String | ID of the policies associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.policies.data.type | String | Type of the policies associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.workspaces.data.id | String | ID of the workspaces associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.workspaces.data.type | String | Type of the workspaces associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.projects.data.id | String | Relationship information for projects associated with the Terraform policy set. |
| Terraform.PolicySet.relationships.workspace-exclusions.data | Unknown | Relationship information for workspace exclusions associated with the Terraform policy set. |
| Terraform.PolicySet.links.self | String | Link to the Terraform policy set data. |
| Terraform.PolicySet.links.self | String | Link to the Terraform policy set. |
| Terraform.PolicySet.links.first | String | Link to the first page of Terraform policy sets. |
| Terraform.PolicySet.links.prev | Unknown | Link to the previous page of Terraform policy sets. |
| Terraform.PolicySet.links.next | Unknown | Link to the next page of Terraform policy sets. |
| Terraform.PolicySet.links.last | String | Link to the last page of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.current-page | Number | Current page number in the pagination of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.page-size | Number | Number of items per page in the pagination of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.prev-page | Unknown | Link to the previous page in the pagination of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.next-page | Unknown | Link to the next page in the pagination of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.total-pages | Number | Total number of pages in the pagination of Terraform policy sets. |
| Terraform.PolicySet.meta.pagination.total-count | Number | Total number of Terraform policy sets. |
Command example
#### Context Example
```json
{
"Terraform": {
"PolicySet": {
"attributes": {
"agent-enabled": false,
"created-at": "2023-11-08T11:25:06.196Z",
"description": null,
"global": false,
"kind": "sentinel",
"name": "test-policy-set",
"policy-count": 1,
"project-count": 0,
"updated-at": "2023-11-08T11:25:06.196Z",
"versioned": false,
"workspace-count": 1
},
"id": "polset-hc2bvqDW8YRgHEt8",
"links": {
"self": "/api/v2/policy-sets/polset-hc2bvqDW8YRgHEt8"
},
"relationships": {
"organization": {
"data": {
"id": "example-org-40dc3b",
"type": "organizations"
}
},
"policies": {
"data": [
{
"id": "pol-ycCqXorxsFjaH5aK",
"type": "policies"
}
]
},
"projects": {
"data": []
},
"workspace-exclusions": {
"data": []
},
"workspaces": {
"data": [
{
"id": "ws-u7kVixWpJYWiERMG",
"type": "workspaces"
}
]
}
},
"type": "policy-sets"
}
}
}
Human Readable Output
Terraform Policy Sets
Organization Policies ids Policy Set name Policy set id Workspaces example-org-40dc3b pol-ycCqXorxsFjaH5aK test-policy-set polset-hc2bvqDW8YRgHEt8 ws-u7kVixWpJYWiERMG
terraform-policies-checks-list
List the policy checks for a Terraform run.
Base Command
terraform-policies-checks-list
Permissions
This command requires a User or Team token that has admin level access to the workspace. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | The run ID to list results for. | Optional |
| policy_check_id | The policy check ID to retrieve details for. | Optional |
| page_number | The page number of the results to return. Default is 1. | Optional |
| page_size | The number of results to return per page. Default is 20, maximum is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Terraform.PolicyCheck.id | String | The policy check ID. |
| Terraform.PolicyCheck.type | String | Type of the Terraform policy check data. |
| Terraform.PolicyCheck.attributes.result.result | Boolean | Overall result of the Terraform policy check. |
| Terraform.PolicyCheck.attributes.result.passed | Number | Number of policy checks that passed. |
| Terraform.PolicyCheck.attributes.result.total-failed | Number | Total number of policy checks that failed. |
| Terraform.PolicyCheck.attributes.result.hard-failed | Number | Number of policy checks that resulted in hard failures. |
| Terraform.PolicyCheck.attributes.result.soft-failed | Number | Number of policy checks that resulted in soft failures. |
| Terraform.PolicyCheck.attributes.result.advisory-failed | Number | Number of policy checks that resulted in advisory failures. |
| Terraform.PolicyCheck.attributes.result.duration-ms | Number | Duration of the policy check execution in milliseconds. |
| Terraform.PolicyCheck.attributes.result.sentinel | Unknown | Sentinel-specific result of the policy check. |
| Terraform.PolicyCheck.attributes.scope | String | Scope or context of the Terraform policy check. |
| Terraform.PolicyCheck.attributes.status | String | Status of the Terraform policy check. |
| Terraform.PolicyCheck.attributes.status-timestamps.queued-at | Date | Timestamp indicating when the Terraform policy check was queued. |
| Terraform.PolicyCheck.attributes.status-timestamps.soft-failed-at | Date | Timestamp indicating when the Terraform policy check encountered a soft failure. |
| Terraform.PolicyCheck.attributes.actions.is-overridable | Boolean | Flag indicating whether the Terraform policy check is overridable. |
| Terraform.PolicyCheck.attributes.permissions.can-override | Boolean | Flag indicating whether the user has permission to override the Terraform policy check. |
| Terraform.PolicyCheck.relationships.run.data.id | String | Unique identifier for the Terraform run associated with the policy check. |
| Terraform.PolicyCheck.relationships.run.data.type | String | Type of the Terraform run associated with the policy check. |
| Terraform.PolicyCheck.links.output | String | Link to the output of the Terraform policy check. |
Command example
!terraform-policies-checks-list run_id=run-8wpCneWr4TLSzfat
Human Readable Output
Terraform Policy Checks
No entries.
terraform-get-events
Retrieves Terraform audit trail events. This command is supported in Cortex XSIAM only and is intended for development and debugging purposes, as it may produce duplicate events.
Base Command
terraform-get-events
Permissions
This command requires the audit-logging feature entitlement for the organization as well as an Audit trail or Organization token. Refer to HashiCorp Terraform API Token Access Levels.
Input
| Argument Name | Description | Required |
|---|---|---|
| from_date | The start date for the audit trails as a relative time expression (e.g., ‘3 days ago’) or an absolute time in ISO 8601 format (e.g., ‘2025-09-01T00:00:00Z’). Default is 1 hour ago. | Optional |
| limit | The maximum number of events to retrieve. Default is 10. | Optional |
| should_push_events | If true, the command will push the events to the Cortex XSIAM dataset; otherwise, it will only display them. Default is false. | Optional |
Context Output
There is no context output for this command.
Command example
!terraform-get-events limit=2
Human Readable Output
Terraform Audit Trail Events
timestamp id version type auth request 2025-09-21T11:24:26.541Z ae66e491-aaaa-457c-8445-9c908ee726aa 0 Resource {“accessor_id”: “user-xxxxxxxxxxxx”, “description”: “example-userA”} {“id”: “4df584d4-7e2a-aaaa-6cc0-4adbefa020e6”} 2025-09-21T11:24:26.541Z ae66e491-bbbb-457c-8445-9c908ee726bb 0 Resource {“accessor_id”: “user-yyyyyyyyyyyy”, “description”: “example-userB”} {“id”: “4df584d4-7e2a-bbbb-6cc0-4adbefa020e6”}
Troubleshooting
- HTTP 429 (Too many requests) Errors
- Typically occur when the 30 requests per second API limit is exceeded when paginating over audit trail events.
- To avoid such errors, reduce the Maximum Number of Audit Events Per Fetch parameter value.
- For additional information, refer to HashiCorp Terraform API Rate Limits.
<~PLATFORM>
License Requirements
The following configuration parameters require the Cortex XSIAM license:
- Fetch events
</~PLATFORM>
Configuration parameters
server_url— Server URL (required)credentials— (required)default_organization_name— Default Organization Namedefault_workspace_id— Default Workspace IDinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetchEvents— Fetch eventseventFetchInterval— Events Fetch Intervalmax_fetch— Maximum Number of Audit Events Per Fetch
Commands (7)
-
terraform-get-eventsRetrieves Terraform audit trail events. This command is supported in Cortex XSIAM only and is intended for development and debugging purposes, as it may produce duplicate events.
-
terraform-plan-getGet the plan JSON file or the plan meta data.
-
terraform-policies-checks-listList the policy checks for a Terraform run.
-
terraform-policies-listList the policies for an organization or get a specific policy.
-
terraform-policy-set-listList the policy sets for an organization or get a specific policy set.
-
terraform-run-actionPerform an action on a Terraform run. The available actions are: apply, cancel, discard, force-cancel, force-execute.
-
terraform-runs-listList runs in a workspace.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from requests import Response import aiohttp from http import HTTPStatus import asyncio from typing import Any from collections.abc import Callable import math from datetime import datetime, timedelta, UTC RUN_HR_KEY_TO_RES_KEY = { "Run id": "id", "Status": "attributes.status", "Plan id": "relationships.plan.data.id", "Planned at": "attributes.status-timestamps.planned-at", } PLAN_HR_KEY_TO_RES_KEY = { "Plan id": "id", "Status": "attributes.status", "Agent Queued at": "attributes.status-timestamps.agent-queued-at", } POLICIES_HR_KEY_TO_RES_KEY = { "Policy id": "id", "Policy name": "attributes.name", "Policy description": "attributes.description", "Kind": "attributes.kind", "Policy Set ids": "relationships.policy-sets.data.id", "Organization id": "relationships.organization.data.id", } SET_HR_KEY_TO_RES_KEY = { "Policy set id": "id", "Policy Set name": "attributes.name", "Description": "attributes.description", "Organization": "relationships.organization.data.id", "Policies ids": "relationships.policies.data.id", "Workspaces": "relationships.workspaces.data.id", "Projects": "relationships.projects.data.id", } CHECK_HR_KEY_TO_RES_KEY = { "Policy check id": "id", "Result": "attributes.result", "Status": "attributes.status", "Scope ": "attributes.scope", } VENDOR = "HashiCorp" PRODUCT = "Terraform" DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DEFAULT_GET_EVENTS_LIMIT = 10 DEFAULT_FETCH_EVENTS_LIMIT = 10000 DEFAULT_AUDIT_TRAIL_PAGE_SIZE = 1000 DEFAULT_AUDIT_TRAIL_FROM_DATE = datetime.now(tz=UTC) - timedelta(hours=1) DEFAULT_AUDIT_TRAIL_MAX_RETRIES = 3 class Client(BaseClient): """A synchronous client for interacting with the HashiCorp Terraform API; used for basic commands""" def __init__( self, url: str, token: str, default_organization_name: str | None = None, default_workspace_id: str | None = None, verify: bool = True, proxy: bool = False, ): self._default_organization_name = default_organization_name self._default_workspace_id = default_workspace_id headers = {"Authorization": f"Bearer {token}"} super().__init__(base_url=url, verify=verify, proxy=proxy, headers=headers) def test_connection(self): return self._http_request("GET", "account/details") def runs_list_request( self, workspace_id: str | None = None, run_id: str | None = None, filter_status: str | None = None, page_number: str | None = None, page_size: str | None = None, ) -> dict: params = {} if not run_id: if filter_status: params["filter[status]"] = filter_status if page_number: params["page[number]"] = page_number if page_size: params["page[size]"] = page_size workspace_id = workspace_id or self._default_workspace_id if not workspace_id: raise DemistoException( "Please provide either, the instance param 'Default Workspace Id' or the command argument 'workspace_id'" ) url_suffix = f"/runs/{run_id}" if run_id else f"/workspaces/{workspace_id}/runs" response = self._http_request("GET", url_suffix, params=params) return response def run_action(self, run_id: str, action: str, comment: str | None = None) -> Response: return self._http_request( "POST", f"runs/{run_id}/actions/{action}", json_data={"comment": comment} if comment and action != "force-execute" else None, headers=self._headers | {"Content-Type": "application/vnd.api+json"}, ok_codes=[200, 202, 403, 404, 409], resp_type="response", ) def get_plan(self, plan_id: str, json_output: bool) -> Response: url_suffix = f'/plans/{plan_id}{"/json-output" if json_output else ""}' return self._http_request("GET", url_suffix, resp_type="response") def list_policies( self, organization_name: str | None = None, policy_kind: str | None = None, policy_name: str | None = None, policy_id: str | None = None, ) -> dict: params = {} if not policy_id: if policy_kind: params["filter[kind]"] = policy_kind if policy_name: params["search[name]"] = policy_name organization_name = organization_name or self._default_organization_name if not organization_name: raise DemistoException( "Please provide either the instance param '\ 'Default Organization Name' or the command argument 'organization_name'" ) url_suffix = f"/policies/{policy_id}" if policy_id else f"/organizations/{organization_name}/policies" response = self._http_request("GET", url_suffix, params=params) return response def list_policy_sets( self, organization_name: str | None, policy_set_id: str | None, versioned: str | None, policy_set_kind: str | None, include: str | None, policy_set_name: str | None, page_number: str | None, page_size: str | None, ) -> dict: params: dict[str, str] = {} if not policy_set_id: if versioned: params["filter[versioned]"] = versioned if policy_set_kind: params["filter[kind]"] = policy_set_kind if include: params["include"] = include if policy_set_name: params["search[name]"] = policy_set_name if page_number: params["page[number]"] = page_number if page_size: params["page[size]"] = page_size organization_name = organization_name or self._default_organization_name if not organization_name: raise DemistoException( "Please provide either the instance param 'Default Organization Name'\ ' or the command argument 'organization_name'" ) url_suffix = f"/policy-sets/{policy_set_id}" if policy_set_id else f"/organizations/{organization_name}/policy-sets" return self._http_request("GET", url_suffix, params=params) def list_policy_checks( self, run_id: str | None, policy_check_id: str | None, page_number: str | None, page_size: str | None ) -> dict: """List Terraform policy checks""" params = {} if page_number: params["page[number]"] = page_number if page_size: params["page[size]"] = page_size url_suffix = f"/runs/{run_id}/policy-checks" if run_id else f"/policy-checks/{policy_check_id}" return self._http_request("GET", url_suffix, params=params) class AsyncClient: """An asynchronous client for interacting with the HashiCorp Terraform API; used for SIEM event collection""" def __init__(self, base_url: str, token: str, verify: bool, proxy: bool): self.base_url = base_url self._headers = {"Authorization": f"Bearer {token}"} self._verify = verify self._proxy_url = handle_proxy().get("http") if proxy else None async def __aenter__(self): self._session = aiohttp.ClientSession(headers=self._headers, connector=aiohttp.TCPConnector(ssl=self._verify)) return self async def __aexit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: exception_traceback = "".join(traceback.format_exception(exc_type, exc_val, exc_tb)) demisto.error(f"AsyncClient context exited with an exception: {exception_traceback}.") else: demisto.debug("AsyncClient context exited normally.") # Always ensure HTTP client session is closed await self._session.close() async def get_audit_trails( self, from_date: str, page_number: int, page_size: int = DEFAULT_AUDIT_TRAIL_PAGE_SIZE, max_retries: int = DEFAULT_AUDIT_TRAIL_MAX_RETRIES, ) -> dict[str, Any]: """ Retrieves audit trails from Terraform. Args: from_date (str): The start date for the audit trails in ISO 8601 format. page_number (int): The page number to retrieve. page_size (int): The number of items per page. Default is 1000. max_retries (int): The maximum number of retries following HTTP 429 errors. Default is 3. Returns: dict[str, Any]: A dictionary containing the audit trails raw API response. """ params: dict[str, str] = {"since": from_date, "page[number]": str(page_number), "page[size]": str(page_size)} url = urljoin(self.base_url, "/organization/audit-trail") backoff_factor = 1 for attempt in range(max_retries): try: attempt_string = f"attempt {attempt + 1}/{max_retries}" demisto.debug(f"Starting request for audit trails ({attempt_string}) using {params=}.") async with self._session.get(url=url, params=params, proxy=self._proxy_url) as response: response.raise_for_status() response_json = await response.json() response_data = response_json.get("data", []) oldest_event_time = newest_event_time = None if response_data: # The first event is the newest, and the last event is the oldest newest_event_time = response_data[0]["timestamp"] oldest_event_time = response_data[-1]["timestamp"] demisto.debug( f"Finished request for audit trails using {params=}. " f"Got {len(response_data)} items: {oldest_event_time=}, {newest_event_time=}." ) return response_json except aiohttp.ClientResponseError as e: if e.status == HTTPStatus.TOO_MANY_REQUESTS and attempt < max_retries - 1: delay = int(backoff_factor * (2**attempt)) # double the back off time each time demisto.debug(f"Got rate limit error ({attempt_string}) using {params=}. Backing off for {delay} seconds.") await asyncio.sleep(delay) else: demisto.error(f"Request failed with status {e.status}: {e.message}") raise raise Exception(f"Failed after {max_retries} attempts to retrieve audit trails using {params=}.") def deduplicate_and_format_events( raw_response: dict[str, Any], all_fetched_ids: set[str], ) -> list[dict[str, Any]]: """ Processes events from a raw API response, deduplicates them, and adds the _time field. Args: raw_response (dict[str, Any]): A dictionary containing the raw API response of the audit trails. all_fetched_ids (set[str]): A set of event IDs that have already been fetched. Returns: list[dict[str, Any]]: A list of new, processed events. """ events = [] for event in raw_response.get("data", []): event_id = event["id"] if event_id in all_fetched_ids: demisto.debug(f"Skipping duplicate {event_id=}.") continue all_fetched_ids.add(event_id) # `arg_to_datetime` does not return `None` since `timestamp` field exists and has a supported format # Added `type: ignore` to silence type checkers and linters event["_time"] = arg_to_datetime(event["timestamp"]).strftime(DATE_FORMAT) # type: ignore [union-attr] events.append(event) return events async def get_audit_trail_events( client: AsyncClient, from_date: str, limit: int, last_fetched_ids: list[str] | None = None, ) -> list[dict[str, Any]]: """ Asynchronously fetches audit trail events from Terraform, handling pagination. Since the API returns events from newest to oldest, pages are fetched in reverse order to process the oldest events first. Args: client (AsyncClient): An instance of the AsyncClient. from_date (str): The start date for the audit trails in ISO 8601 format. limit (int): The maximum number of events to retrieve. last_fetched_ids (list[str]): A list of IDs of events that have already been fetched. Returns: list[dict[str, Any]]: A list of new audit trail events, sorted from oldest to newest. """ last_fetched_ids = last_fetched_ids or [] all_fetched_ids = set(last_fetched_ids) all_events = [] # Get the first page to determine the total number of pages since the API returns events from newest to oldest first_page_raw_response = await client.get_audit_trails(from_date=from_date, page_number=1) total_pages = first_page_raw_response.get("pagination", {}).get("total_pages", 1) # Calculate the number of pages to fetch to meet the limit page_size = DEFAULT_AUDIT_TRAIL_PAGE_SIZE required_pages = math.ceil(limit / page_size) pages_to_fetch = min(total_pages, int(required_pages)) # Determine the range of pages to fetch (from last to first) start_page = total_pages stop_page = max(0, total_pages - pages_to_fetch) # stop page not included! if pages_to_fetch > 0: # Create tasks to fetch pages concurrently, from oldest to newest audit_trail_tasks = [ client.get_audit_trails(from_date=from_date, page_number=page_number) for page_number in range(start_page, stop_page, -1) ] # Gather responses from all API requests. If one page fails, all will fail to avoid missing events raw_responses = await asyncio.gather(*audit_trail_tasks) for raw_response in raw_responses: new_events = deduplicate_and_format_events(raw_response, all_fetched_ids) # Since we are fetching pages in reverse (oldest to newest) all_events.extend(new_events) # Sort all collected events by timestamp (oldest to newest) and return up to the limit all_events.sort(key=lambda event: event["timestamp"]) return all_events[:limit] async def get_events_command(client: AsyncClient, args: dict[str, Any]) -> tuple[list[dict[str, Any]], CommandResults]: """ Implements the `terraform-get-events` command. Gets audit trail events using the AsyncClient. Args: client (AsyncClient): An instance of the AsyncClient. args (dict[str, Any]): The command arguments. Returns: tuple[list[dict[str, Any]], CommandResults]: A tuple of the events list and the CommandResults with human-readable output. """ from_date = (arg_to_datetime(args.get("from_date")) or DEFAULT_AUDIT_TRAIL_FROM_DATE).strftime(DATE_FORMAT) limit = arg_to_number(args.get("limit")) or DEFAULT_GET_EVENTS_LIMIT events = await get_audit_trail_events(client, from_date, limit) return events, CommandResults(readable_output=tableToMarkdown(name="Terraform Audit Trail Events", t=events)) async def fetch_events_command( client: AsyncClient, last_run: dict, max_fetch: int, ) -> tuple[dict[str, Any], list[dict[str, Any]]]: """ Implements `fetch-events` command. Fetches audit trail events using the AsyncClient. Args: client (AsyncClient): An instance of the AsyncClient. last_run (dict): The last run object. max_fetch (int): The maximum number of events to fetch. Returns: tuple[dict[str, Any], list[dict[str, Any]]]: A tuple of the the next run object and a list of fetched events. """ demisto.debug(f"Starting fetching events with {last_run=}.") from_date = last_run.get("from_date") or DEFAULT_AUDIT_TRAIL_FROM_DATE.strftime(DATE_FORMAT) last_fetched_ids = last_run.get("last_fetched_ids", []) all_events = await get_audit_trail_events( client=client, from_date=from_date, limit=max_fetch, last_fetched_ids=last_fetched_ids, ) if not all_events: demisto.debug(f"No new events found since {last_run=}.") return last_run, [] # Events are sorted by `timestamp` in ascending order inside `get_audit_trail_events` newest_event_timestamp = all_events[-1]["timestamp"] demisto.debug(f"Got {len(all_events)} deduplicated events with {newest_event_timestamp=}.") # Get the IDs of the events that have the newest timestamp new_last_fetched_ids = [event["id"] for event in all_events if event["timestamp"] == newest_event_timestamp] next_run = {"from_date": newest_event_timestamp, "last_fetched_ids": new_last_fetched_ids} demisto.debug(f"Updating {next_run=} after fetching {len(all_events)} events.") return next_run, all_events def runs_list_command(client: Client, args: Dict[str, Any]) -> CommandResults: workspace_id = args.get("workspace_id") run_id = args.get("run_id") filter_status = args.get("filter_status") page_number = args.get("page_number") page_size = args.get("page_size") res = client.runs_list_request(workspace_id, run_id, filter_status, page_number, page_size) # when run_id is provided, it returns a single run instead of a list data = [res.get("data", {})] if run_id else res.get("data", []) hr_items = [ {hr_key: demisto.get(run, response_key) for hr_key, response_key in RUN_HR_KEY_TO_RES_KEY.items()} for run in data ] command_results = CommandResults( outputs_prefix="Terraform.Run", outputs_key_field="data.id", outputs=res, readable_output=tableToMarkdown("Terraform Runs", hr_items, removeNull=True), ) return command_results def run_action_command(client: Client, args: Dict[str, Any]) -> str: run_id = args.get("run_id") action = args.get("action") comment = args.get("comment") if not run_id or not action: raise DemistoException("run_id and action are required") if action == "force-execute" and comment: raise DemistoException("comment parameter is invalid for force-execute action") res = client.run_action(run_id=run_id, action=action, comment=comment) action_msg = f"queued an {action} request for run id {run_id}" if res.status_code == 202: return f"Successfully {action_msg}" else: raise DemistoException(f'Error occurred when {action_msg}: {res.json().get("errors",[{}])[0].get("title")}') def plan_get_command(client: Client, args: Dict[str, Any]) -> CommandResults | dict[str, Any]: plan_id = args.get("plan_id") json_output = argToBoolean(args.get("json_output", False)) if not plan_id: raise DemistoException("plan_id is required") res = client.get_plan(plan_id, json_output) if json_output: return fileResult(filename=f"{plan_id}.json", data=res.content, file_type=EntryType.ENTRY_INFO_FILE) res_json = res.json() plan = res_json.get("data", {}) hr_plan = {hr_key: demisto.get(plan, response_key) for hr_key, response_key in PLAN_HR_KEY_TO_RES_KEY.items()} command_results = CommandResults( outputs_prefix="Terraform.Plan", outputs_key_field="id", outputs=plan, raw_response=res_json, readable_output=tableToMarkdown("Terraform Plan", hr_plan), ) return command_results def policies_list_command(client: Client, args: Dict[str, Any]) -> CommandResults: organization_name = args.get("organization_name") policy_kind = args.get("policy_kind") policy_name = args.get("policy_name") policy_id = args.get("policy_id") res = client.list_policies(organization_name, policy_kind, policy_name, policy_id) # when policy_id is provided, it returns a single policy instead of a list data = [res.get("data", {})] if policy_id else res.get("data", []) hr_items = [ {hr_key: demisto.dt(policy, response_key) for hr_key, response_key in POLICIES_HR_KEY_TO_RES_KEY.items()} for policy in data ] command_results = CommandResults( outputs_prefix="Terraform.Policy", outputs_key_field="id", outputs=data, raw_response=res, readable_output=tableToMarkdown("Terraform Policies", hr_items, removeNull=True), ) return command_results def policy_set_list_command(client: Client, args: Dict[str, Any]) -> CommandResults: organization_name = args.get("organization_name") policy_set_id = args.get("policy_set_id") versioned = args.get("versioned") policy_set_kind = args.get("policy_set_kind") include = args.get("include") policy_set_name = args.get("policy_set_name") page_number = args.get("page_number") page_size = args.get("page_size") res = client.list_policy_sets( organization_name, policy_set_id, versioned, policy_set_kind, include, policy_set_name, page_number, page_size ) # when policy_set_id is provided, it returns a single policy set instead of a list data = [res.get("data", {})] if policy_set_id else res.get("data", []) hr_items = [ {hr_key: demisto.dt(policy_set, response_key) for hr_key, response_key in SET_HR_KEY_TO_RES_KEY.items()} for policy_set in data ] return CommandResults( outputs_prefix="Terraform.PolicySet", outputs_key_field="id", outputs=data, raw_response=res, readable_output=tableToMarkdown("Terraform Policy Sets", hr_items, removeNull=True), ) def policies_checks_list_command(client: Client, args: Dict[str, Any]) -> CommandResults: run_id = args.get("run_id") policy_check_id = args.get("policy_check_id") page_number = args.get("page_number") page_size = args.get("page_size") res = client.list_policy_checks(run_id, policy_check_id, page_number, page_size) # when policy_check_id is provided, it returns a single check instead of a list data = [res.get("data", {})] if policy_check_id else res.get("data", []) hr_items = [ {hr_key: demisto.get(policy_check, response_key) for hr_key, response_key in CHECK_HR_KEY_TO_RES_KEY.items()} for policy_check in data ] return CommandResults( outputs_prefix="Terraform.PolicyCheck", outputs_key_field="id", outputs=data, raw_response=res, readable_output=tableToMarkdown("Terraform Policy Checks", hr_items, removeNull=True), ) def test_module(client: Client) -> str: try: client.test_connection() except Exception as e: if "Unauthorized" in str(e): raise DemistoException("Unauthorized: Please be sure you put a valid API Token") raise e return "ok" async def main() -> None: params: dict[str, Any] = demisto.params() args: dict[str, Any] = demisto.args() command: str = demisto.command() url = params.get("server_url", "https://app.terraform.io/api/v2").rstrip("/") token = params.get("credentials", {}).get("password") default_workspace_id = params.get("default_workspace_id") default_organization_name = params.get("default_organization_name") verify_certificate: bool = not params.get("insecure", False) proxy = params.get("proxy", False) is_fetch_events = params.get("isFetchEvents", False) max_fetch = arg_to_number(params.get("max_fetch")) or DEFAULT_FETCH_EVENTS_LIMIT demisto.debug(f"Command being called is {command}") sync_commands: dict[str, Callable] = { "terraform-runs-list": runs_list_command, "terraform-run-action": run_action_command, "terraform-plan-get": plan_get_command, "terraform-policies-list": policies_list_command, "terraform-policy-set-list": policy_set_list_command, "terraform-policies-checks-list": policies_checks_list_command, } async_commands: tuple[str, str] = ("terraform-get-events", "fetch-events") try: def _initialize_sync_client() -> Client: return Client(url, token, default_organization_name, default_workspace_id, verify_certificate, proxy) def _initialize_async_client() -> AsyncClient: return AsyncClient(base_url=url, token=token, verify=verify_certificate, proxy=proxy) if command == "test-module": client: Client = _initialize_sync_client() test_results = test_module(client) if is_fetch_events: async with _initialize_async_client() as async_client: await fetch_events_command(async_client, last_run={}, max_fetch=1) return_results(test_results) elif command in sync_commands: client = _initialize_sync_client() command_results = sync_commands[command](client, args) return_results(command_results) elif command in async_commands and (is_xsiam() or is_platform()): async with _initialize_async_client() as async_client: if command == "fetch-events": last_run = demisto.getLastRun() next_run, events = await fetch_events_command(async_client, last_run=last_run, max_fetch=max_fetch) send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(next_run) elif command == "terraform-get-events": should_push_events = argToBoolean(args.pop("should_push_events", False)) events, command_results = await get_events_command(async_client, args) return_results(command_results) if should_push_events: send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) else: raise NotImplementedError(f"{command} command is not implemented.") except Exception as e: return_error(str(e)) if __name__ in ["__main__", "builtin", "builtins"]: asyncio.run(main())