Fortanix DSM
Manage Secrets and Protect Confidential Data using Fortanix Data Security Manager.
Identity and Access Management · Fortanix DSM
Details
| ID | Fortanix DSM |
|---|---|
| Provider | Fortanix |
| Category | Identity and Access Management |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Manage Secrets and Protect Confidential Data using Fortanix Data Security Manager (DSM)
Authorize Cortext XSOAR to Fortanix DSM
User/password or Client Certificate Auth Method
These fields accept the Username and Password parameters for a user or App. These credentials may also be used for mutual-TLS using a client key and certificate. The may be signed by a Trusted CA if Fortanix DSM is configured accordingly.
API KEY Auth Method
An easy and quick way to test the integration is to specify the Basic Authentication token parameter from the Fortanix DSM App’s API KEY.
Configure Fortanix DSM in Cortex
| Parameter | Description | Required |
|---|---|---|
| Fortanix DSM server endpoint | URL e.g., https://amer.smartkey.io | True |
| Username / App UUID / Certificate | If Certificate, specify PEM | False |
| Password / App Secret / Private Key | Private key cannot be encrypted | False |
| API Key | Alternative to Username and Password | False |
| Trust any server certificate (insecure) | Ignores TLS, not recommended | False |
| Use system proxy settings | Whether to use proxy settings from the Environment | False |
| Group UUID to list secrets from | Filter the secrets accessible to a single DSM Group | False |
| Data protection key used for encryption and decryption | Also configure the Cipher Mode | False |
| Encryption and decryption mode | e.g. FPE, GCM, CBC | 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.
fortanix-list-secrets
List secrets from one or more specified group(s)
Base Command
fortanix-list-secrets
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | Group UUID to list secrets from (overrides integration settings). | Optional |
| state | Current state of the secret (default show all except deleted or destroyed). Possible values are: enabled, disabled, preactive, active, deactivated, compromised, deleted, destroyed. | Optional |
| page | Page offset to return (100 results at a time). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret.Name | string | Secret Name |
| Fortanix.Secret.ID | string | Secret ID (Key ID or kid) |
| Fortanix.Secret.Group | unknown | Group ID |
fortanix-get-secret-metadata
Get the secret metadata without exposing its value
Base Command
fortanix-get-secret-metadata
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Name of the secret (mandatory, unless kid is specified). | Optional |
| kid | Secret UUID (unless name is unspecified). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret | unknown | Secret metadata, if successful |
fortanix-fetch-secret
Retrieve the secret value
Base Command
fortanix-fetch-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| kid | Secret UUID (obtained from the list-secrets command). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret.Value | unknown | Sensitive value of the secret |
fortanix-new-secret
Import a new secret
Base Command
fortanix-new-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Name of the secret. | Required |
| value | Sensitive value of the secret. | Required |
| group_id | Group UUID to import the secret into. | Optional |
| metadata | List of key-value pairs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret | unknown | Secret metadata, if successful |
fortanix-rotate-secret
Update an existing secret, which will be rotated
Base Command
fortanix-rotate-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Name of the secret. | Required |
| value | Sensitive value of the secret. | Required |
| metadata | List of key-value pairs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret | unknown | Secret metadata, if successful |
fortanix-delete-secret
Delete the secret
Base Command
fortanix-delete-secret
Input
| Argument Name | Description | Required |
|---|---|---|
| kid | Secret UUID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Secret.Result | unknown | Deletion status |
fortanix-invoke-plugin
Invoke a Fortanix Plugin that is executed in a Confidential Computing enclave
Base Command
fortanix-invoke-plugin
Input
| Argument Name | Description | Required |
|---|---|---|
| pid | Plugin UUID. | Required |
| input | Arbitrary user input based on the plugin. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Plugin.Output | unknown | Plugin invocation output |
fortanix-encrypt
Protects data using key configured in Fortanix DSM
Base Command
fortanix-encrypt
Input
| Argument Name | Description | Required |
|---|---|---|
| data | User data. | Required |
| key | Key name used for protection (overrides configured). | Optional |
| mode | Encryption mode (overrides configured). Possible values are: FPE, GCM, CBC. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Data.Cipher | unknown | Encryption output |
fortanix-decrypt
Reveal data using key configured in Fortanix DSM
Base Command
fortanix-decrypt
Input
| Argument Name | Description | Required |
|---|---|---|
| cipher | Protected ciphertext. | Required |
| kid | Key UUID for decryption (overrides configured). | Optional |
| mode | Decryption mode (overrides configured). Possible values are: FPE, GCM, CBC. | Optional |
| iv | Nonce or initialization vector (if any). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Fortanix.Data.Plain | unknown | Decryption output |
Configuration parameters
server— Fortanix DSM server endpoint (required)credentials— Username / App UUID / Certificatetoken— API Keyinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsgroup_ids— Group UUID to list secrets fromprotection_key— Data protection key used for encryption and decryptionprotection_mode— Encryption and decryption mode
Commands (9)
-
fortanix-decryptReveal data using key configured in Fortanix DSM
-
fortanix-delete-secretDelete the secret
-
fortanix-encryptProtects data using key configured in Fortanix DSM
-
fortanix-fetch-secretRetrieve the secret value
-
fortanix-get-secret-metadataGet the secret metadata without exposing its value
-
fortanix-invoke-pluginInvoke a Fortanix Plugin that is executed in a Confidential Computing enclave
-
fortanix-list-secretsList secrets from one or more specified group(s)
-
fortanix-new-secretImport a new secret
-
fortanix-rotate-secretUpdate an existing secret, which will be rotated
"""Fortanix-DSM Integration for Cortex XSOAR - Unit Tests file This Unit Tests in this file validate the FortanixDSM Integration based on pytest as mandated by Cortex XSOAR contribution requirements. They verify that the integration is behaving as expected during CI/CD pipeline. Test Execution -------------- Unit tests can be checked in 3 ways: - Using the command `lint` of demisto-sdk. The command will build a dedicated docker instance for your integration locally and use the docker instance to execute your tests in a dedicated docker instance. - From the command line using `pytest -v` or `pytest -vv` - From PyCharm Example with demisto-sdk (from the content root directory): demisto-sdk lint -i Packs/Fortanix-DSM/Integrations/FortanixDSM Coverage -------- There could be one or more unit tests per command function and is executed with specific parameters, wherein the output is checked against an expected result. Unit tests should be self contained and should not interact with external resources like (API, devices, ...). To isolate the code from external resources you need to mock the API of the external resource using pytest-mock: https://github.com/pytest-dev/pytest-mock/ In the following code we configure requests-mock (a mock of Python requests) before each test to simulate the API calls to the Fortanix DSM API. This way we can have full control of the API behavior and focus only on testing the logic inside the integration code. We recommend to use outputs from the API calls and use them to compare the results when possible. See the ``test_data`` directory that contains the data we use for comparison, in order to reduce the complexity of the unit tests and avoding to manually mock all the fields. NOTE: we do not have to import or build a requests-mock instance explicitly. requests-mock library uses a pytest specific mechanism to provide a requests_mock instance to any function with an argument named requests_mock. More Details ------------ More information about Unit Tests in Cortex XSOAR: https://xsoar.pan.dev/docs/integrations/unit-testing """ import json import pytest from CommonServerPython import DemistoException def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) def test_integration(requests_mock): from FortanixDSM import Client, get_headers, test_module url = "https://fortanix.test" path = "sys/v1/session/auth" mock_response = { "token_type": "Bearer", "expires_in": 120, "entity_id": "some_account_id", "access_token": "some_bearer_token", } requests_mock.post(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers=get_headers(None, None, "some_api_key")) response = test_module(client) assert response == "ok" requests_mock.post(f"{url}/{path}") with pytest.raises(DemistoException): response = test_module(client) assert not response def test_list_secrets(requests_mock): """ Tests fortanix-list-secrets command function. Given: - requests_mock instance to generate the appropriate list_secrets API response when the correct list_secrets API request is performed. - group_id argument When: - Running the 'list-secrets-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, list_secrets_command url = "https://fortanix.test" path = "crypto/v1/keys" name = "secret 101" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" gid = "07f85883-adaf-4a6c-a040-ffed46dfd349" params = f"obj_type=SECRET&sort=name%3Aasc&group_id={gid}" mock_response = [ {"name": name, "kid": kid, "group_id": gid, "state": "Active", "enabled": True}, {"name": name, "kid": kid, "group_id": gid, "state": "Active", "enabled": False}, ] requests_mock.get(f"{url}/{path}?{params}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"group_id": gid, "state": "Active", "page": 0} response = list_secrets_command(client, args) assert response.outputs_prefix == "Fortanix.Secret" """ mocking a single secret response here, but; for a list of secrets, the response from the request needs to have each secret.state defined and the method response differs from the request response. """ mock_response = [ { "Name": name, "ID": kid, "Group": gid, }, { "Name": name, "ID": kid, "Group": gid, }, ] assert response.outputs == mock_response def test_get_secret_metadata(requests_mock): """ Tests fortanix-get-secret-metadata command function. Given: - requests_mock instance to generate the appropriate get_secret_metadata API response when the correct get_secret_metadata API request is performed. - kid argument When: - Running the 'get-secret-metadata-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, list_secrets_command url = "https://fortanix.test" path = "crypto/v1/keys" name = "secret 101" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" gid = "07f85883-adaf-4a6c-a040-ffed46dfd349" params = "obj_type=SECRET&sort=name%3Aasc" mock_response = {"name": name, "kid": kid, "group_id": gid, "obj_type": "SECRET"} requests_mock.get(f"{url}/{path}/{kid}?{params}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"kid": kid} response = list_secrets_command(client, args) assert response.outputs_prefix == "Fortanix.Secret" assert response.outputs == mock_response args["page"] = 2 args["state"] = "deleted" response = list_secrets_command(client, args, gid) assert response.outputs_prefix == "Fortanix.Secret" assert response.outputs == mock_response def test_no_secrets(requests_mock): """ Tests fortanix-get-secret-metadata command function. Given: - requests_mock instance to generate the appropriate get_secret_metadata API response when the correct get_secret_metadata API request is performed. - name argument When: - Running the 'get-secret-metadata-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, list_secrets_command url = "https://fortanix.test" path = "crypto/v1/keys" name = "secret 101" params = "obj_type=SECRET&sort=name%3Aasc" requests_mock.get(f"{url}/{path}?{params}", json={}) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"name": name} response = list_secrets_command(client, args) assert response.outputs_prefix == "No secrets found" assert response.outputs == {} def test_fetch_secret(requests_mock): """ Tests fortanix-fetch-secret command function. Given: - requests_mock instance to generate the appropriate fetch_secret API response when the correct fetch_secret API request is performed. - name argument When: - Running the 'fetch-secret-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, fetch_secret_command url = "https://fortanix.test" path = "crypto/v1/keys/export" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" value = "SGVsbG8gV29ybGQ=" mock_response = {"value": value} requests_mock.post(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"kid": kid} response = fetch_secret_command(client, args) assert response.outputs_prefix == "Fortanix.Secret.Value" mock_response = [{"Value": "Hello World"}] assert response.outputs == mock_response with pytest.raises(DemistoException): response = fetch_secret_command(client, {}) assert not response def test_new_secret(requests_mock): """ Tests fortanix-new-secret command function. Given: - requests_mock instance to generate the appropriate import_secret API response when the correct import_secret API request is performed. - name argument - value argument When: - Running the 'new_secret_command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, import_secret_command url = "https://fortanix.test" path = "crypto/v1/keys" gid = "07f85883-adaf-4a6c-a040-ffed46dfd349" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" aid = "0a9bcbbe-1919-4c57-89e4-b521f4d9ff28" name = "new secr3t" mock_response = {"kid": kid, "name": name, "acct_id": aid, "obj_type": "SECRET", "never_exportable": False, "state": "Active"} requests_mock.put(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = { "name": name, "value": "rand0m", "metadata": {"key": "attribute"}, } response = import_secret_command(client, args, gid) assert response.outputs_prefix == "Fortanix.Secret" assert response.outputs == mock_response with pytest.raises(DemistoException): response = import_secret_command(client, {"name": name}) assert not response response = import_secret_command(client, {"value": "val"}) assert not response requests_mock.put(f"{url}/{path}") response = import_secret_command(client, {"name": name, "value": "val"}) assert not response def test_rotate_secret(requests_mock): """ Tests fortanix-rotate-secret command function. Given: - requests_mock instance to generate the appropriate import_secret API response when the correct import_secret API request is performed. - name argument - value argument When: - Running the 'rotate_secret_command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, import_secret_command url = "https://fortanix.test" path = "crypto/v1/keys/rekey" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" aid = "0a9bcbbe-1919-4c57-89e4-b521f4d9ff28" name = "new secr3t" mock_response = {"kid": kid, "name": name, "acct_id": aid, "obj_type": "SECRET", "never_exportable": False, "state": "Active"} requests_mock.post(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"name": name, "value": "rand0m", "metadata": "key1=attr1,key2=attr2"} response = import_secret_command(client, args, None, True) assert response.outputs_prefix == "Fortanix.Secret" assert response.outputs == mock_response requests_mock.post(f"{url}/{path}") with pytest.raises(DemistoException): response = import_secret_command(client, args, None, True) assert not response def test_delete_secret(requests_mock): """ Tests fortanix-delete-secret command function. Given: - requests_mock instance to generate the appropriate delete_secret API response when the correct delete_secret API request is performed. - kid argument When: - Running the 'delete-secret-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, delete_secret_command url = "https://fortanix.test" path = "crypto/v1/keys" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" mock_response = [{"Result": "OK"}] requests_mock.delete(f"{url}/{path}/{kid}", json={}) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"kid": kid} response = delete_secret_command(client, args) assert response.outputs_prefix == "Fortanix.Secret.Result" assert response.outputs == mock_response with pytest.raises(DemistoException): response = delete_secret_command(client, {}) assert not response requests_mock.delete(f"{url}/{path}/{kid}", json={}) response = delete_secret_command(client, args) assert response.outputs == {} def test_invoke_plugin(requests_mock): """ Tests fortanix-invoke-plugin command function. Given: - requests_mock instance to generate the appropriate invoke-plugin API response when the correct invoke-plugin API request is performed. - pid argument - input argument When: - Running the 'invoke-plugin-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, invoke_plugin_command url = "https://fortanix.test" path = "sys/v1/plugins" pid = "3599796b-7b18-49c3-aad8-9758af24fbf9" mock_response = {"plugin_output": "Plugin_value", "another_key": True, "numeric_key": 90210} requests_mock.post(f"{url}/{path}/{pid}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"pid": pid, "input": "random"} response = invoke_plugin_command(client, args) assert response.outputs_prefix == "Fortanix.Plugin.Output" assert response.outputs == [mock_response] args["input"] = ( "eyJraWQiOiAiN2ExNjFhM2YtOGQ1My00MmRlLTgwY" "2QtOTJmYjAxN2M1YTEyIiwgImNpcGhlciI6ICJ1Mk" "tNY0FVRjFqc2lmSmZoOTl1V3F3PT0iLCAiaXYiOiAi" "cjdIZUhkdUhTWjFJckNDNnM3TUcwdz09IiwgIm1vZG" "UiOiAiQ0JDIn0=" ) response = invoke_plugin_command(client, args) assert response.outputs_prefix == "Fortanix.Plugin.Output" assert response.outputs == [mock_response] with pytest.raises(DemistoException): response = invoke_plugin_command(client, {}) assert not response def test_encrypt(requests_mock): """ Tests fortanix-encrypt command function. Given: - requests_mock instance to generate the appropriate encrypt API response when the correct encrypt API request is performed. - key argument - data argument - mode argument When: - Running the 'encrypt-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, encrypt_command url = "https://fortanix.test" path = "crypto/v1/encrypt" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" mock_response = {"kid": kid, "cipher": "u2KMcAUF1jsifJfh99uWqw==", "iv": "r7HeHduHSZ1IrCC6s7MG0w=="} requests_mock.post(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"key": "key_name", "data": "Hello World", "mode": "CBC"} response = encrypt_command(client, args) cipher = ( "eyJraWQiOiAiN2ExNjFhM2YtOGQ1My00MmRlLTgwY2QtOTJmYjAxN2M1YTEyIi" "wgImNpcGhlciI6ICJ1MktNY0FVRjFqc2lmSmZoOTl1V3F3PT0iLCAiaXYiOiAi" "cjdIZUhkdUhTWjFJckNDNnM3TUcwdz09IiwgIm1vZGUiOiAiQ0JDIn0=" ) assert response.outputs_prefix == "Fortanix.Data.Cipher" assert response.outputs == [{"Cipher": cipher}] with pytest.raises(DemistoException): response = encrypt_command(client, {}) assert not response def test_decrypt(requests_mock): """ Tests fortanix-decrypt command function. Given: - requests_mock instance to generate the appropriate decrypt API response when the correct decrypt API request is performed. - cipher argument When: - Running the 'decrypt-command'. Then: - Checks the output of the command function with the expected output. """ from FortanixDSM import Client, decrypt_command url = "https://fortanix.test" path = "crypto/v1/decrypt" kid = "7a161a3f-8d53-42de-80cd-92fb017c5a12" cipher = ( "eyJraWQiOiAiN2ExNjFhM2YtOGQ1My00MmRlLTgwY2QtOTJmYjAxN2M1YTEyIi" "wgImNpcGhlciI6ICJ1MktNY0FVRjFqc2lmSmZoOTl1V3F3PT0iLCAiaXYiOiAi" "cjdIZUhkdUhTWjFJckNDNnM3TUcwdz09IiwgIm1vZGUiOiAiQ0JDIn0=" ) mock_response = { "kid": kid, "plain": "u2KMcAUF1jsifJfh99uWqw==", } requests_mock.post(f"{url}/{path}", json=mock_response) client = Client(base_url=url, verify=False, headers={"Authentication": "Basic some_api_key"}) args = {"cipher": cipher} response = decrypt_command(client, args) assert response.outputs_prefix == "Fortanix.Data.Plain" assert response.outputs == [{"Plain": "u2KMcAUF1jsifJfh99uWqw=="}] with pytest.raises(DemistoException): response = decrypt_command(client, {}) assert not response @pytest.mark.parametrize("method", ["base64", "json", "metadata", "headers"]) def test_helpers(method): """ Given: - A string represent a method name When: - Running the helper functions. Then: - Verify that the exceptions are raised """ from FortanixDSM import get_headers, getMetadata, isBase64, isJSON empty = {} if method == "base64": res = isBase64(None) assert not res elif method == "json": res = isJSON(empty) assert not res elif method == "metadata": res = getMetadata(empty) assert not res elif method == "headers": res = get_headers("some_user", "some_passwd") assert res assert isinstance(res, dict) assert res["Authorization"] res = get_headers(None, None, None, "some_bearer") assert res assert isinstance(res, dict) assert res["Authorization"]