HashiCorp Vault
Manage Secrets and Protect Sensitive Data through HashiCorp Vault.
Authentication & Identity Management · HashiCorp Vault
Details
| ID | HashiCorp Vault |
|---|---|
| Provider | IBM |
| Category | Authentication & Identity Management |
| From Version | 5.0.0 |
| Docker Image | demisto/vendors-sdk:1.0.0.10120494 |
| Supported Modules | Agentix XSIAM |
README
Secure, store, and tightly control access to tokens, passwords, certificates, and encryption keys for protecting secrets and other sensitive data using HashiCorp Vault. This integration fetches credentials. For more information, see Managing Credentials.
This integration was integrated and tested with version 1.12.2 of HashiCorp Vault.
Authentication
The integration supports the following auth methods:
Userpass Auth Method
You are required to fill in only the Username / Role ID parameter with the username and Password / Secret ID parameter with the password. For more details, see the HashiCorp Vault documentation.
Token Auth Method
You are required to fill in only the Authentication token parameter. For more details, see the HashiCorp Vault documentation.
AppRole Auth Method
You are required to fill in only the Username / Role ID parameter with the role ID and Password / Secret ID parameter with the secret ID, and check the Use AppRole Auth Method checkbox. For more details, see the HashiCorp Vault documentation.
Configure HashiCorp Vault in Cortex
| Parameter | Description | Required |
|---|---|---|
| HashiCorp server URL (e.g., https://192.168.0.1:8200) | The server URL | True |
| Use AppRole Auth Method | Set as true if you are using the AppRole method for authentication. | False |
| Username / Role ID | The username for the Hashicorp vault. | False |
| Password / Secret ID | The password for the Hashicorp vault. | False |
| Cache the authentication token | enables caching of the authentication token. | False |
| Authentication token | A token for authentication for the Hashicorp vault. (Use instead of password and username.) | False |
| Vault enterprise namespace | The namespace used for the vault by the user. | False |
| Trust any certificate (not secure) | Mark as true to make unverified HTTP requests. | False |
| Use system proxy settings | Mark as true to use proxy settings. | False |
| Fetches credentials | Mark as true to fetch credentials to the Cortex XSOAR credentials vault. | False |
| CSV list of secrets engine types to fetch secrets from | Possible values are KV, Cubbyhole, AWS. | False |
| Concat username to credential object name | Should be used in case there are several secrets under the same folder in order to make the credential object unique. | 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.
hashicorp-list-secrets-engines
List all secrets engines that exist in HashiCorp Vault.
Base Command
hashicorp-list-secrets-engines
Input
There are no input arguments.
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Engine.Type | string | Secrets engine type. |
| HashiCorp.Engine.Path | string | Secrets engine path in HashiCorp. |
| HashiCorp.Engine.Description | string | Secrets engine description. |
| HashiCorp.Engine.Accessor | string | Secrets engine accessor. |
Command example
!hashicorp-list-secrets-engines
hashicorp-list-secrets
List secrets (names) for a specified KV engine.
Base Command
hashicorp-list-secrets
Input
| Argument Name | Description | Required |
|---|---|---|
| engine | Engine path, e.g.,”secret/”. Use the list-secrets-engines command to retrieve the engine path. | Required |
| version | The version of the KV engine. Possible values are: 1, 2. Default is 1. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Secret.Path | string | Secret path |
Command example
!hashicorp-list-secrets
hashicorp-get-secret-metadata
Returns information about a specified secret in a specified KV V2 engine.
Base Command
hashicorp-get-secret-metadata
Input
| Argument Name | Description | Required |
|---|---|---|
| engine_path | KV Engine path, e.g., “kv/”. | Required |
| secret_path | Secret path, e.g., “secret”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Secret.Created | date | Secret creation time. |
| HashiCorp.Secret.Version.Destroyed | boolean | Is the version destroyed. |
| HashiCorp.Secret.Version.Created | number | Version creation time. |
| HashiCorp.Secret.Version.Deleted | date | Version deletion time. |
| HashiCorp.Secret.Updated | date | Secret last updated time. |
| HashiCorp.Secret.Engine | string | Secret engine type. |
| HashiCorp.Secret.CurrentVersion | number | Secret current version. |
| HashiCorp.Secret.Path | string | Secret path. |
| HashiCorp.Secret.CustomMetadata | unknown | The custom metadata saved in the secret. |
Command example
!hashicorp-get-secret-metadata engine_path=secret secret_path=test
hashicorp-delete-secret
Deletes the data under a specified secret given the secret path. Performs a soft delete that allows you to run the hashicorp-undelete-secret command if necessary (for KV V2 engine).
Base Command
hashicorp-delete-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| secret_path | Secret path, e.g., “secret”. | Required |
| engine_path | Engine path, e.g.,”secret/”. | Required |
| versions | CSV list of secret versions to delete. | Required |
Context Output
There is no context output for this command.
Command example
!hashicorp-delete-secret engine_path=secret secret_path=test versions=2
hashicorp-undelete-secret
Undeletes (restores) a secret on HashiCorp (for KV V2 engine).
Base Command
hashicorp-undelete-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| secret_path | Secret path, e.g., “secret”. | Required |
| engine_path | Engine path, e.g.,”secret/”. | Required |
| versions | CSV list of secret versions to undelete (restore). | Required |
Context Output
There is no context output for this command.
Command example
!hashicorp-undelete-secret engine_path=secret secret_path=test versions=2
hashicorp-destroy-secret
Permanently deletes a secret (for KV V2 engine).
Base Command
hashicorp-destroy-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| secret_path | Secret path, .e.g., “secret”. | Required |
| engine_path | Engine path, e.g.,”secret/”. | Required |
| versions | CSV list of secret versions to permanently delete. | Required |
Context Output
There is no context output for this command.
Command example
!hashicorp-destroy-secret engine_path=secret secret_path=test versions=2
hashicorp-disable-engine
When a secrets engine is no longer needed, it can be disabled. All secrets under the engine are revoked and the corresponding vault data and configurations are removed.
Base Command
hashicorp-disable-engine
Input
| Argument Name | Description | Required |
|---|---|---|
| path | Path of the secrets engine to disable. | Required |
Context Output
There is no context output for this command.
Command example
!hashicorp-disable-engine path=secret
hashicorp-enable-engine
***!hashicorp-disable-engine path=secret
Enables a new secrets engine at the specified path.
Base Command
hashicorp-enable-engine
Input
| Argument Name | Description | Required |
|---|---|---|
| path | The path where the secrets engine will be mounted. | Required |
| type | Type of backend, e.g., “aws”. | Required |
| description | Friendly description of the mount. | Optional |
| default_lease_ttl | The default lease duration, specified as a string duration, e.g., “5s” or “30m”. | Optional |
| max_lease_ttl | The maximum lease duration, specified as a string duration, e.g., “5s” or “30m”. | Optional |
| force_no_cache | Whether to disable caching. | Optional |
| audit_non_hmac_request_keys | CSV list of keys that will not be HMAC’d by audit devices in the request data object. | Optional |
| audit_non_hmac_response_keys | CSV list of keys that will not be HMAC’d by audit devices in the response data object. | Optional |
| listing_visibility | Whether to show this mount in the UI-specific listing endpoint. Default is hidden. Possible values are: unauth, hidden. | Optional |
| passthrough_request_headers | CSV list of headers to add to allow list and pass from the request to the backend. | Optional |
| kv_version | KV version to mount. Set to “2” for mount KV V2. Possible values are: 1, 2. | Optional |
| local | Specifies if the secrets engine is a local mount only. Local mounts are not replicated, nor (if a secondary) removed by replication. Supported only in Vault Enterprise. | Optional |
| seal_wrap | Enable seal wrapping for the mount. Supported only in Vault Enterprise. | Optional |
Context Output
There is no context output for this command.
Command example
!hashicorp-enable-engine path=secret type=AWS
hashicorp-list-policies
Lists all configured policies.
Base Command
hashicorp-list-policies
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Policy.Name | string | Policy name. |
Command example
hashicorp-list-policies
hashicorp-get-policy
Get information for a policy.
Base Command
hashicorp-get-policy
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Policy name. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Policy.Name | string | Policy name. |
| HashiCorp.Policy.Rule.Path | string | Policy rule path. |
| HashiCorp.Policy.Rule.Capabilities | unknown | Policy rule capabilities. |
Command example
!hashicorp-get-policy name=secret
hashicorp-seal-vault
If you suspect your data has been compromised, you can seal your vault to prevent access to your secrets.
Base Command
hashicorp-seal-vault
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
Command example
!hashicorp-seal-vault
hashicorp-unseal-vault
Use a single master key share to unseal the vault. If the master key shares threshold is met, the key will attempt to unseal the vault. Otherwise, this API must be called until the threshold is met.
Base Command
hashicorp-unseal-vault
Input
| Argument Name | Description | Required |
|---|---|---|
| key | Single master key . | Optional |
| reset | Reset the unseal project. Possible values are: true. | Optional |
Context Output
There is no context output for this command.
Command example
!hashicorp-unseal-vault
hashicorp-configure-engine
Configure a secrets engine to fetch secrets from.
Base Command
hashicorp-configure-engine
Input
| Argument Name | Description | Required |
|---|---|---|
| path | The engine path, e.g., “secret/”. | Required |
| folder | Specific folder to fetch secrets from, e.g., “secret-folder/”. (Supported only for engine type KV2.) | Optional |
| type | The engine type, e.g., “KV”. Possible values are: KV, Cubbyhole, AWS. | Required |
| version | The engine version (for KV engines). Possible values are: 1, 2. | Optional |
| aws_roles_list | A comma-delimited list of roles names to generate credentials for. If not mentioned, we will generate credentials for all roles in the path.(used for only for AWS). | Optional |
| aws_method | A parameter to indicate which type of request we would like to use to generate credentials(used for only for AWS). | Optional |
Context Output
There is no context output for this command.
Command example
!hashicorp-configure-engine type=type version=2 path=path ttl=3600
Human Readable Output
Engine configured successfully
hashicorp-reset-configuration
Reset the engine configuration.
Base Command
hashicorp-reset-configuration
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
Command example
!hashicorp-reset-configuration
Human Readable Output
Successfully reset the engine configuration.
hashicorp-create-token
Create a new authentication token.
Base Command
hashicorp-create-token
Input
| Argument Name | Description | Required |
|---|---|---|
| role_name | The name of the token role. | Optional |
| policies | CSV list of policies for the token. This must be a subset of the policies belonging to the token making the request, unless root. If policies are not specified, all policies of the calling token are applied to the new token. | Optional |
| meta | A map of string-to-string valued metadata. This is passed through to the audit devices. | Optional |
| no_parent | If true and set by a root caller, the token will not have the parent token of the caller. This creates a token with no parent. Possible values are: true, false. | Optional |
| no_default_policy | If true the default policy will not be included in this token’s policy set. Possible values are: true, false. | Optional |
| renewable | If set to false, the token cannot be renewed past its initial TTL. If set to true, the token can be renewed up to the system/mount maximum TTL. Possible values are: true, false. | Optional |
| ttl | The TTL (lease duration) period of the token, provided as “10m” or “1h”, where hour is the largest suffix. If not provided, the token is valid for the default lease TTL, or indefinitely if the root policy is used. | Optional |
| explicit_max_ttl | If set, the token will have an explicit max TTL applied to it. The maximum token TTL cannot be changed later, and unlike with normal tokens, updates to the system/mount max TTL value will have no effect at renewal time. The token can never be renewed or used past the value set at issue time. | Optional |
| display_name | The display name of the token. | Optional |
| num_uses | The maximum number of times the token can be used. Supply this argument to create a one-time-token, or limited use token. The value of 0 has no limit to the number of uses. | Optional |
| period | If specified, the token will be periodic. It will not have a maximum TTL (unless an “explicit-max-ttl” is also set), but every renewal will use the given period. Requires a root/sudo token to use. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.Auth.Token | string | Authentication token. |
| HashiCorp.Auth.Policy | unknown | Authentication policies. |
| HashiCorp.Auth.LeaseDuration | number | Authentication lease duration in seconds, 0 if indefinitely. |
Command example
!hashicorp-create-token display_name=token explicit_max_ttl=3600 renewable=false
hashicorp-generate-role-secret
Generates and issues a new SecretID on an existing AppRole.
Base Command
hashicorp-generate-role-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| role_name | The name of the AppRole. | Required |
| meta_data | Metadata to be tied to the SecretID. | Optional |
| cidr_list | Comma separated string or list of CIDR blocks enforcing secret IDs to be used from specific set of IP addresses. | Optional |
| token_bound_cidrs | Comma-separated string or list of CIDR blocks. | Optional |
| num_uses | Number of times this SecretID can be used, after which the SecretID expires. A value of zero will allow unlimited uses. | Optional |
| ttl_seconds | Duration in seconds after which this SecretID expires. A value of zero will allow the SecretID to not expire. | Optional |
Context Output
There is no context output for this command.
Command example
!hashicorp-generate-role-secret role_name=my-role
Human Readable Output
SecretID:123
hashicorp-get-role-id
Retrieves the AppRole ID for a specified role.
Base Command
hashicorp-get-role-id
Input
| Argument Name | Description | Required |
|---|---|---|
| role_name | The name of the AppRole. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| HashiCorp.AppRole.Id | string | AppRole ID. |
| HashiCorp.AppRole.Name | string | AppRole Name. |
Command example
!hashicorp-get-role-id role_name=my-role
Human Readable Output
| Id | Name |
|---|---|
| role_id | role_name |
Additional Information
- In order to fetch credentials from HashiCorp Vault, the relevant secrets engines must be configured with the integration so it can pull the data from them. To configure an engine with the integration, use the configure-engine command.
- The default fetch rate for fetch-credentials is 10 minutes. This is configurable with the server parameter vault.module.cache.expire
Known Limitations
Currently the integration is able to fetch credentials from the following engines:
- K/V Versions 1,2
- Cubbyhole
- AWS
The following commands are limited to the K/V V2 engine
- hashicorp-list-secrets
- hashicorp-get-secret-metadata
- hashicorp-delete-secret
- hashicorp-undelete-secret
- hashicorp-destroy-secret
Configuration parameters
server— HashiCorp server URL (e.g., https://192.168.0.1:8200) (required)use_approle— Use AppRole Auth Methodcredentials— Username / Role IDcache_token— Cache the authentication tokentoken— Authentication tokencredentials_token—namespace— Vault enterprise namespaceunsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetchCredentials— Fetches credentialsengines— CSV list of secrets engine types to fetch secrets fromconcat_username_to_cred_name— Concat username to credential object name
Commands (17)
-
hashicorp-configure-engineConfigure a secrets engine to fetch secrets from.
-
hashicorp-create-tokenCreates a new authentication token.
-
hashicorp-delete-secretDeletes the data under a specified secret given the secret path. Performs a soft delete that allows you to run the hashicorp-undelete-secret command if necessary (for KV V2 engine).
-
hashicorp-destroy-secretPermanently deletes a secret (for KV V2 engine).
-
hashicorp-disable-engineWhen a secrets engine is no longer needed, it can be disabled. All secrets under the engine are revoked and the corresponding vault data and configurations are removed.
-
hashicorp-enable-engineEnables a new secrets engine at the specified path.
-
hashicorp-generate-role-secretGenerates and issues a new SecretID on an existing AppRole.
-
hashicorp-get-policyGet information for a policy.
-
hashicorp-get-role-idRetrieves the AppRole ID for a specified role.
-
hashicorp-get-secret-metadataReturns information about a specified secret in a specified KV V2 engine.
-
hashicorp-list-policiesLists all configured policies.
-
hashicorp-list-secretsList secrets (names) for a specified KV engine.
-
hashicorp-list-secrets-enginesList all secrets engines that exist in HashiCorp Vault.
-
hashicorp-reset-configurationReset the engines configuration.
-
hashicorp-seal-vaultIf you suspect your data has been compromised, you can seal your vault to prevent access to your secrets.
-
hashicorp-undelete-secretUndeletes (restores) a secret on HashiCorp (for KV V2 engine).
-
hashicorp-unseal-vaultUse a single master key share to unseal the vault. If the master key shares threshold is met, the vault will attempt to unseal the vault. Otherwise, this API must be called until the threshold is met.
import demistomock as demisto # noqa: F401 import hcl from CommonServerPython import * # noqa: F401 """ GLOBAL VARIABLES """ CREDENTIALS = demisto.params().get("credentials", {}) USERNAME = None PASSWORD = None # Used to make sure we generate a new token before the old one expires, in seconds, only relevant to AWS AWS_TOKEN_OVERLAP_TIME = 600 if CREDENTIALS: USERNAME = CREDENTIALS.get("identifier") PASSWORD = CREDENTIALS.get("password") VERIFY_SSL = not demisto.params().get("unsecure", False) TOKEN = demisto.params().get("credentials_token", {}).get("password") or demisto.params().get("token") CACHE_TOKEN = argToBoolean(demisto.params().get("cache_token", "false") or "false") NAMESPACE = demisto.params().get("namespace") USE_APPROLE_AUTH_METHOD = argToBoolean(demisto.params().get("use_approle", "false") or "false") BASE_URL = demisto.params().get("server", "") SERVER_URL = BASE_URL + "/v1" DEFAULT_STATUS_CODES = {429, 472, 473} TIME_BUFFER = 5 """ HELPER FUNCTIONS """ def get_headers(): headers = {"Content-Type": "application/json", "X-Vault-Request": "true"} if TOKEN: # pragma: no cover headers["X-Vault-Token"] = TOKEN if NAMESPACE: # pragma: no cover headers["X-Vault-Namespace"] = NAMESPACE return headers def login(): # pragma: no cover if USE_APPROLE_AUTH_METHOD: path = "auth/approle/login" # type: ignore body = {"role_id": USERNAME, "secret_id": PASSWORD} else: path = "auth/userpass/login/" + USERNAME # type: ignore body = {"password": PASSWORD} integration_context = get_integration_context() url = urljoin(SERVER_URL, path) payload = json.dumps(body) headers = get_headers() res = requests.request("POST", url, headers=headers, data=payload, verify=VERIFY_SSL, allow_redirects=True) if (res.status_code < 200 or res.status_code >= 300) and res.status_code not in DEFAULT_STATUS_CODES: try: error_body = res.json() if "errors" in error_body and isinstance(error_body["errors"], list): error_body = ";".join(error_body["errors"]) if len(error_body["errors"]) > 0 else "None" except Exception as ex: demisto.error(f"Error in login (parsing error msg): {ex}") error_body = res.content return_error(f"Login failed. Status code: {str(res.status_code)}, details: {error_body}") auth_res = res.json() if not auth_res or "auth" not in auth_res or "client_token" not in auth_res["auth"]: return_error("Could not authenticate user") integration_context.update( { "lease_expiration": int(time.time()) + int(auth_res["auth"]["lease_duration"]), "auth_token": auth_res["auth"]["client_token"], } ) set_integration_context(integration_context) return auth_res["auth"]["client_token"] def send_request(path, method="get", body=None, params=None, headers=None): body = body if body is not None else {} params = params if params is not None else {} url = urljoin(SERVER_URL, path) headers = headers if headers is not None else get_headers() res = requests.request(method, url, headers=headers, data=json.dumps(body), params=params, verify=VERIFY_SSL) if res.status_code < 200 or res.status_code >= 300: try: error_body = res.json() if "errors" in error_body and isinstance(error_body["errors"], list): error_body = ";".join(error_body["errors"]) if len(error_body["errors"]) > 0 else "None" except Exception as ex: demisto.error(f"Error in send_request (parsing error msg): {ex}") error_body = res.content return_error(f"Request failed. Status code: {str(res.status_code)}, details: {error_body}") if res.content: return res.json() return "" """ FUNCTIONS """ def generate_role_secret_command(): """ Generate a secret ID for a specified AppRole in the authentication system. Args: args (dict): A dictionary containing the following keys: - 'role_name' (required): The name of the AppRole for which the secret ID is generated. - 'meta_data': Metadata associated with the secret ID. - 'cidr_list': Comma-separated list of CIDR blocks from which requests using the secret ID are allowed. - 'token_bound_cidrs': Comma-separated list of CIDR blocks to restrict tokens issued with this secret ID. - 'num_uses': Number of times the secret ID can be used before it expires. - 'ttl_seconds': Time duration in seconds for which the secret ID remains valid. Returns: CommandResults: The command results object containing the response from the Vault server as readable output. """ args = demisto.args() role_name = args.get("role_name") meta_data = args.get("meta_data") cidr_list = argToList(args.get("cidr_list", "")) token_bound_cidrs = argToList(args.get("token_bound_cidrs", "")) num_uses = arg_to_number(args.get("num_uses", "")) ttl_seconds = arg_to_number(args.get("ttl_seconds", "")) path = f"/auth/approle/role/{role_name}/secret-id" body = { "metadata": meta_data, "cidr_list": cidr_list, "token_bound_cidrs": token_bound_cidrs, "ttl": ttl_seconds, "num_uses": num_uses, } body = remove_empty_elements(body) response = send_request(path=path, method="post", body=body) return_results(CommandResults(readable_output=response)) def get_role_id_command(): """ Retrieve the Role ID associated with a specified AppRole from the authentication system. Args: args (dict): A dictionary containing the following keys: - 'role_name' (required): The name of the AppRole for which the Role ID is retrieved. Returns: CommandResults: The command results object containing the retrieved Role ID and role name as outputs. """ args = demisto.args() role_name = args.get("role_name") path = f"/auth/approle/role/{role_name}/role-id" response = send_request(path=path, method="get", body={"role_name": role_name}) role_id = response.get("data", {}).get("role_id", "") if response else "" if not role_id: raise DemistoException(f"Role ID not found for AppRole '{role_name}'. Please check the role name and try again.") return_results(CommandResults(outputs_prefix="HashiCorp.AppRole", outputs={"Id": role_id, "Name": role_name})) def list_secrets_engines_command(): # pragma: no cover res = list_secrets_engines() if not res: return_error("No engines found") mapped_engines = [ {"Path": k, "Type": v.get("type"), "Description": v.get("description"), "Accessor": v.get("accessor")} for k, v in res.get("data", {}).items() ] headers = ["Path", "Type", "Description", "Accessor"] demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("HashiCorp Vault Secrets Engines", mapped_engines, headers=headers, removeNull=True), "EntryContext": {"HashiCorp.Engine(val.Path===obj.Path)": createContext(mapped_engines, removeNull=True)}, } ) def list_secrets_engines(): path = "sys/mounts" return send_request(path) def list_secrets_command(): # pragma: no cover engine = demisto.args()["engine"] version = demisto.args().get("version") res = list_secrets(engine, version) if not res or "data" not in res: return_error("Secrets not found") mapped_secrets = [{"Path": k} for k in res["data"].get("keys", [])] demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( "HashiCorp Vault Secrets in engine path: " + engine, mapped_secrets, removeNull=True ), "EntryContext": {"HashiCorp.Secret(val.Path===obj.Path)": createContext(mapped_secrets)}, } ) def list_secrets(engine_path, version, folder=None): path = engine_path if version == "2": path = urljoin(path, "metadata") if folder: path += os.path.join("/", folder) params = {"list": "true"} return send_request(path, "get", params=params) def get_secret_metadata_command(): # pragma: no cover engine_path = demisto.args()["engine_path"] secret_path = demisto.args()["secret_path"] res = get_secret_metadata(engine_path, secret_path) if not res or "data" not in res: return_error("Secret not found") data = res["data"] secret_headers = ["Engine", "Created", "Updated", "CurrentVersion"] version_headers = ["Number", "Created", "Deleted", "Destroyed"] mapped_secret = { "Path": secret_path, "Engine": engine_path, "Created": data.get("created_time"), "Updated": data.get("updated_time"), "CurrentVersion": data.get("current_version"), "CustomMetadata": data.get("custom_metadata"), } mapped_versions = [ {"Number": k, "Created": v["created_time"], "Deleted": v["deletion_time"], "Destroyed": v["destroyed"]} for k, v in data.get("versions", {}).items() ] hr = tableToMarkdown("Secret metadata", mapped_secret, headers=secret_headers, removeNull=True) if mapped_versions: hr += tableToMarkdown("Versions", mapped_versions, headers=version_headers, removeNull=True) mapped_secret["Version"] = mapped_versions demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": hr, "EntryContext": {"HashiCorp.Secret(val.Path===obj.Path)": createContext(mapped_secret, removeNull=True)}, } ) def get_secret_metadata(engine_path, secret_path): path = engine_path + "/metadata/" + secret_path return send_request(path, "get") def delete_secret_command(): # pragma: no cover engine_path = demisto.args()["engine_path"] secret_path = demisto.args()["secret_path"] versions = argToList(demisto.args()["versions"]) delete_secret(engine_path, secret_path, versions) demisto.results("Secret versions deleted successfully") def delete_secret(engine_path, secret_path, versions): path = urljoin(engine_path, urljoin("delete/", secret_path)) body = {"versions": versions} return send_request(path, "post", body=body) def undelete_secret_command(): # pragma: no cover engine_path = demisto.args()["engine_path"] secret_path = demisto.args()["secret_path"] versions = argToList(demisto.args()["versions"]) undelete_secret(engine_path, secret_path, versions) demisto.results("Secret versions undeleted successfully") def undelete_secret(engine_path, secret_path, versions): path = urljoin(engine_path, urljoin("undelete/", secret_path)) body = {"versions": versions} return send_request(path, "post", body=body) def destroy_secret_command(): # pragma: no cover engine_path = demisto.args()["engine_path"] secret_path = demisto.args()["secret_path"] versions = argToList(demisto.args()["versions"]) destroy_secret(engine_path, secret_path, versions) demisto.results("Secret versions destroyed successfully") def destroy_secret(engine_path, secret_path, versions): path = urljoin(engine_path, urljoin("destroy/", secret_path)) body = {"versions": versions} return send_request(path, "post", body=body) def list_policies_command(): # pragma: no cover res = list_policies() if not res or "policies" not in res: return_error("No policies found") mapped_policies = [{"Name": i} for i in res["policies"]] demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("HashiCorp Vault Policies", mapped_policies, removeNull=True), "EntryContext": {"HashiCorp.Policy(val.Name===obj.Name)": createContext(mapped_policies, removeNull=True)}, } ) def list_policies(): path = "/sys/policy" return send_request(path, "get") def get_policy_command(): # pragma: no cover name = demisto.args()["name"] res = get_policy(name) if not res or "rules" not in res: return_error("Policy not found") rules = hcl.loads(res["rules"]) mapped_rules = [{"Path": k, "Capabilities": v["capabilities"]} for k, v in rules.get("path", {}).items()] mapped_policy = {"Name": res["name"], "Rule": mapped_rules} demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("HashiCorp Vault Policy - " + name, mapped_rules, removeNull=True), "EntryContext": {"HashiCorp.Policy(val.Name===obj.Name)": createContext(mapped_policy, removeNull=True)}, } ) def get_policy(policy_name): path = "sys/policy/" + policy_name return send_request(path, "get") def disable_engine_command(): # pragma: no cover path = demisto.args()["path"] disable_engine(path) demisto.results("Engine disabled successfully") def disable_engine(engine_path): path = "sys/mounts/" + engine_path return send_request(path, "delete") def enable_engine_command(): # pragma: no cover path = demisto.args()["path"] engine_type = demisto.args()["type"] description = demisto.args().get("description") default_lease_ttl = demisto.args().get("default_lease_ttl") max_lease_ttl = demisto.args().get("max_lease_ttl") force_no_cache = demisto.args().get("force_no_cache") audit_non_hmac_request_keys = argToList(demisto.args().get("audit_non_hmac_request_keys", [])) audit_non_hmac_response_keys = argToList(demisto.args().get("audit_non_hmac_response_keys", [])) listing_visibility = demisto.args().get("listing_visibility") passthrough_request_headers = argToList(demisto.args().get("passthrough_request_headers", [])) kv_version = demisto.args().get("kv_version") local = demisto.args().get("local") seal_wrap = demisto.args().get("seal_wrap") enable_engine( path, engine_type, description, default_lease_ttl, max_lease_ttl, force_no_cache, audit_non_hmac_request_keys, audit_non_hmac_response_keys, listing_visibility, passthrough_request_headers, kv_version, local, seal_wrap, ) demisto.results("Engine enabled successfully") def enable_engine( path, engine_type, description, default_lease_ttl, max_lease_ttl, force_no_cache, audit_non_hmac_request_keys, audit_non_hmac_response_keys, listing_visibility, passthrough_request_headers, kv_version, local, seal_wrap, ): # pragma: no cover path = "sys/mounts/" + path body = {"type": engine_type, "config": {}} if description: body["description"] = description if default_lease_ttl: body["config"]["default_lease_ttl"] = default_lease_ttl if max_lease_ttl: body["config"]["max_lease_ttl"] = max_lease_ttl if force_no_cache: body["config"]["force_no_cache"] = force_no_cache if audit_non_hmac_request_keys: body["config"]["audit_non_hmac_request_keys"] = audit_non_hmac_request_keys if audit_non_hmac_response_keys: body["config"]["audit_non_hmac_response_keys"] = audit_non_hmac_response_keys if listing_visibility: body["config"]["listing_visibility"] = listing_visibility if passthrough_request_headers: body["config"]["passthrough_request_headers"] = passthrough_request_headers if kv_version: body["options"] = {"version": kv_version} if local: body["local"] = local if seal_wrap: body["seal_wrap"] = seal_wrap if not body["config"]: del body["config"] return send_request(path, "post", body=body) def seal_vault_command(): # pragma: no cover seal_vault() demisto.results("Vault sealed successfully") def seal_vault(): path = "sys/seal" return send_request(path, "put") def unseal_vault_command(): # pragma: no cover reset = demisto.args().get("reset") key = demisto.args().get("key") if not key and not reset: return_error("Either key or reset must be provided") res = unseal_vault(key, reset) if not res: return_error("Could not retrieve unseal state") mapped_unseal = { "Sealed": res.get("sealed"), "Threshold": res.get("t"), "Shares": res.get("n"), "Progress": res.get("progress"), } demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("HashiCorp Vault Unseal", mapped_unseal, removeNull=True), } ) def unseal_vault(key, reset): path = "sys/unseal" body = {} if reset: body["reset"] = reset elif key: body["key"] = key return send_request(path, "put", body=body) def create_token_command(): # pragma: no cover role_name = demisto.args().get("role_name") policies = argToList(demisto.args().get("policies", [])) meta = demisto.args().get("meta") no_parent = demisto.args().get("no_parent") no_default_policy = demisto.args().get("no_default_policy") renewable = demisto.args().get("renewable") ttl = demisto.args().get("ttl") explicit_max_ttl = demisto.args().get("explicit_max_ttl") display_name = demisto.args().get("display_name") num_uses = demisto.args().get("num_uses") period = demisto.args().get("period") res = create_token( role_name, policies, meta, no_parent, no_default_policy, renewable, ttl, explicit_max_ttl, display_name, num_uses, period ) if not res or "auth" not in res: return_error("Could not get authentication token") auth = res["auth"] mapped_auth = {"Token": auth.get("client_token"), "Policy": auth.get("policies"), "LeaseDuration": auth.get("lease_duration")} demisto.results( { "Type": entryTypes["note"], "Contents": res, "ContentsFormat": formats["json"], "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("Token successfully created", mapped_auth, removeNull=True), "EntryContext": {"HashiCorp.Auth(val.Token===obj.Token)": createContext(mapped_auth, removeNull=True)}, } ) def create_token( role_name, policies, meta, no_parent, no_default_policy, renewable, ttl, explicit_max_ttl, display_name, num_uses, period ): # pragma: no cover path = "auth/token/create" body = {} if role_name: body["role_name"] = role_name if policies: body["policies"] = policies if meta: body["meta"] = meta if no_parent: body["no_parent"] = no_parent if no_default_policy: body["no_default_policy"] = no_default_policy if renewable: body["renewable"] = renewable if ttl: body["ttl"] = ttl if explicit_max_ttl: body["explicit_max_ttl"] = explicit_max_ttl if display_name: body["display_name"] = display_name if num_uses: body["num_uses"] = num_uses if period: body["period"] = period return send_request(path, "post", body=body) def configure_engine_command(): # pragma: no cover engine_path = demisto.args()["path"] engine_type = demisto.args()["type"] version = demisto.args().get("version") folder = demisto.args().get("folder") aws_roles_list = demisto.args().get("aws_roles_list") aws_method = demisto.args().get("aws_method") ttl = demisto.args().get("ttl") configure_engine( engine_path, engine_type, version, folder=folder, aws_roles_list=aws_roles_list, aws_method=aws_method, ttl=ttl ) demisto.results("Engine configured successfully") def reset_config_command(): # pragma: no cover set_integration_context({"configs": []}) demisto.results("Successfully reset the engines configuration") def configure_engine( engine_path, engine_type, version, folder=None, ttl="3600", aws_roles_list=None, aws_method=None ): # pragma: no cover engine_conf = {"type": engine_type, "path": engine_path, "ttl": ttl} if version: engine_conf["version"] = str(version) if folder: engine_conf["folder"] = folder if aws_roles_list: engine_conf["aws_roles_list"] = aws_roles_list if aws_method: engine_conf["aws_method"] = aws_method ENGINE_CONFIGS.append(engine_conf) # pylint: disable=E0606 set_integration_context({"configs": ENGINE_CONFIGS}) # pylint: disable=E0606 def fetch_credentials(): # pragma: no cover credentials = [] engines_to_fetch_from = [] engines = argToList(demisto.params().get("engines", [])) identifier = demisto.args().get("identifier") concat_username_to_cred_name = argToBoolean(demisto.params().get("concat_username_to_cred_name") or "false") if len(engines) == 0: return_error("No secrets engines specified") for engine_type in engines: engines_to_fetch = list(filter(lambda e: e["type"] == engine_type, ENGINE_CONFIGS)) engines_to_fetch_from += engines_to_fetch if len(engines_to_fetch_from) == 0: return_error("Engine type not configured, Use the configure-engine command to configure a secrets engine.") for engine in engines_to_fetch_from: if engine["type"] == "KV": if "version" not in engine: return_error("Version not configured for KV engine, re-configure the engine") if engine["version"] == "1": credentials += get_kv1_secrets(engine["path"], concat_username_to_cred_name) elif engine["version"] == "2": credentials += get_kv2_secrets(engine["path"], concat_username_to_cred_name, engine.get("folder")) elif engine["type"] == "Cubbyhole": credentials += get_ch_secrets(engine["path"], concat_username_to_cred_name) elif engine["type"] == "AWS": aws_roles_list = [] if engine.get("aws_roles_list"): aws_roles_list = engine.get("aws_roles_list").split(",") credentials += get_aws_secrets(engine["path"], concat_username_to_cred_name, aws_roles_list, engine.get("aws_method")) if identifier: credentials = list(filter(lambda c: c.get("name", "") == identifier, credentials)) demisto.credentials(credentials) def get_kv1_secrets(engine_path, concat_username_to_cred_name=False): # pragma: no cover path = engine_path params = {"list": "true"} res = send_request(path, "get", params=params) secrets = [] if not res or "data" not in res: return [] for secret in res["data"].get("keys", []): secret_data = get_kv1_secret(engine_path, secret) for k, v in secret_data.get("data", {}).items(): if concat_username_to_cred_name: name = f"{secret}_{k}" else: name = secret secrets.append({"user": k, "password": v, "name": name}) return secrets def get_kv1_secret(engine_path, secret): path = engine_path + secret return send_request(path, "get") def get_kv2_secrets(engine_path, concat_username_to_cred_name=False, folder=None): # pragma: no cover secrets = [] res = list_secrets(engine_path, "2", folder) if not res or "data" not in res: return [] for secret in res["data"].get("keys", []): if str(secret).endswith("/") and secret.replace("/", "") != folder: demisto.debug(f"Could not get secrets from path: {secret}") continue secret_data = get_kv2_secret(engine_path, secret, folder) secret_info = secret_data.get("data", {}).get("data", {}) for k in secret_data.get("data", {}).get("data", {}): if concat_username_to_cred_name: name = f"{secret}_{k}" else: name = secret secrets.append({"user": k, "password": secret_info[k], "name": name}) return secrets def get_kv2_secret(engine_path, secret, folder=None): path = urljoin(engine_path, "data/") if folder: path += os.path.join(folder) path += secret return send_request(path, "get") def get_ch_secrets(engine_path, concat_username_to_cred_name=False): # pragma: no cover path = engine_path params = {"list": "true"} res = send_request(path, "get", params=params) secrets = [] if not res or "data" not in res: return [] for secret in res["data"].get("keys", []): secret_data = get_ch_secret(engine_path, secret) for k, v in secret_data.get("data", {}).items(): if concat_username_to_cred_name: name = f"{secret}_{k}" else: name = secret secrets.append({"user": k, "password": v, "name": name}) return secrets def get_aws_secrets(engine_path, concat_username_to_cred_name, aws_roles_list, aws_method): secrets = [] roles_list_url = engine_path + "/roles" demisto.debug(f"roles_list_url: {roles_list_url}") params = {"list": "true"} res = send_request(roles_list_url, "get", params=params) if not res or "data" not in res: return [] for role in res["data"].get("keys", []): if aws_roles_list and role not in aws_roles_list: continue role_url = urljoin(engine_path, urljoin("/roles/", role)) demisto.debug(f"role_url: {role_url}") role_data = send_request(role_url, "get") if not role_data or "data" not in role_data: return [] credential_type = role_data["data"].get("credential_type") if aws_method: if aws_method == "POST": credential_type = "sts" else: credential_type = "iam_user" if credential_type != "iam_user": method = "POST" credential_type = "sts" else: method = "GET" credential_type = "creds" generate_credentials_url = urljoin(engine_path + "/", urljoin(credential_type, "/" + role)) demisto.debug(f"generate_credentials_url: {generate_credentials_url}") body = {} if "role_arns" in role_data["data"]: body["role_arns"] = role_data["data"].get("role_arns", []) aws_credentials = send_request(generate_credentials_url, method, body=body) if not aws_credentials or "data" not in aws_credentials: return [] access_key = aws_credentials["data"].get("access_key") secret_key = aws_credentials["data"].get("secret_key") if aws_credentials["data"].get("security_token"): secret_key = secret_key + "@@@" + aws_credentials["data"].get("security_token") if concat_username_to_cred_name: role = f"{role}_{access_key}" secrets.append({"user": access_key, "password": secret_key, "name": role}) return secrets def get_ch_secret(engine_path, secret): path = engine_path + secret return send_request(path, "get") """ EXECUTION CODE """ if __name__ in ("__main__", "__builtin__", "builtins"): # pragma: no cover handle_proxy() integration_context = get_integration_context() demisto.debug("Executing command: " + demisto.command()) if USERNAME and PASSWORD: if TOKEN: return_error("You can only specify one login method, please choose username and password or authentication token") else: if not CACHE_TOKEN: TOKEN = login() else: lease_expiration = integration_context.get("lease_expiration") if not lease_expiration: demisto.debug("No existing lease; creating a new one.") TOKEN = login() elif lease_expiration <= int(time.time()) - TIME_BUFFER: demisto.debug("Existing lease expired; creating a new one.") TOKEN = login() else: demisto.debug("Existing lease still available; reusing it.") TOKEN = integration_context.get("auth_token") elif not TOKEN: return_error("Either an authentication token or user credentials must be provided") if not integration_context or "configs" not in integration_context: integration_context["configs"] = [] ENGINE_CONFIGS = integration_context["configs"] try: command = demisto.command() if command == "test-module": demisto.results("ok") elif command == "fetch-credentials": fetch_credentials() elif command == "hashicorp-list-secrets-engines": list_secrets_engines_command() elif command == "hashicorp-list-secrets": list_secrets_command() elif command == "hashicorp-list-policies": list_policies_command() elif command == "hashicorp-get-policy": get_policy_command() elif command == "hashicorp-get-secret-metadata": get_secret_metadata_command() elif command == "hashicorp-delete-secret": delete_secret_command() elif command == "hashicorp-undelete-secret": undelete_secret_command() elif command == "hashicorp-destroy-secret": destroy_secret_command() elif command == "hashicorp-disable-engine": disable_engine_command() elif command == "hashicorp-enable-engine": enable_engine_command() elif command == "hashicorp-seal-vault": seal_vault_command() elif command == "hashicorp-unseal-vault": unseal_vault_command() elif command == "hashicorp-create-token": create_token_command() elif command == "hashicorp-configure-engine": configure_engine_command() elif command == "hashicorp-reset-configuration": reset_config_command() elif command == "hashicorp-generate-role-secret": generate_role_secret_command() elif command == "hashicorp-get-role-id": get_role_id_command() except Exception as e: demisto.debug(f"An error occurred: {e}") return_error(f"An error occurred: {e}")