diff --git a/README.md b/README.md index 8e414bb..dc754e5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This app supports the following popular secrets backends: | [AWS Systems Manager Parameter Store](https://aws.amazon.com/secrets-manager/) | [Other: Key/value pairs](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) | [AWS credentials](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html) (see Usage section below) | | [HashiCorp Vault](https://www.vaultproject.io) | [K/V Version 2](https://www.vaultproject.io/docs/secrets/kv/kv-v2)
[K/V Version 1](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1) | [Token](https://www.vaultproject.io/docs/auth/token)
[AppRole](https://www.vaultproject.io/docs/auth/approle)
[AWS](https://www.vaultproject.io/docs/auth/aws)
[Kubernetes](https://www.vaultproject.io/docs/auth/kubernetes) | | [Delinea/Thycotic Secret Server](https://delinea.com/products/secret-server) | [Secret Server Cloud](https://github.com/DelineaXPM/python-tss-sdk#secret-server-cloud)
[Secret Server (on-prem)](https://github.com/DelineaXPM/python-tss-sdk#initializing-secretserver)| [Access Token Authorization](https://github.com/DelineaXPM/python-tss-sdk#access-token-authorization)
[Domain Authorization](https://github.com/DelineaXPM/python-tss-sdk#domain-authorization)
[Password Authorization](https://github.com/DelineaXPM/python-tss-sdk#password-authorization)
| +| [CyberARK AIM](https://www.cyberark.com/) | [Password](https://www.cyberark.com/what-is/secrets-management/) | Token
Username/Password | ## Screenshots @@ -85,6 +86,12 @@ The Delinea/Thycotic Secret Server provider requires the `python-tss-sdk` librar pip install nautobot-secrets-providers[thycotic] ``` +### CyberARK AIM + +```no-highlight +pip install nautobot-secrets-providers[cyberark] +``` + ### Enabling Secrets Providers To ensure Nautobot Secrets Providers is automatically re-installed during future upgrades, create a file named `local_requirements.txt` (if not already existing) in the Nautobot root directory (alongside `requirements.txt`) and list the `secrets` package: @@ -228,6 +235,34 @@ PLUGINS_CONFIG = { - `token` - (optional) Required for 'Access Token Authorization'. - `username` - (optional) Required for 'Secret Server Cloud', 'Password Authorization', 'Domain Authorization'. +### CyberARK AIM + +The CyberARK AIM Secret Provider includes only one provider: + +- **`CyberARK AIM`** + + This provider allows to get a password from CyberARK Password Vault using API. + The user configured to access to CyberARK needs to be able to see the Vault where the account is stored to see it, if not it will return an error. + +#### Configuration + +```python +PLUGINS_CONFIG = { + "nautobot_secrets_providers": { + "cyberark": { + "url": os.getenv("NAUTOBOT_CYBERARK_URL", None), + "token": os.getenv("NAUTOBOT_CYBERARK_TOKEN", None), + "username": os.getenv("NAUTOBOT_CYBERARK_USERNAME", None), + "password": os.getenv("NAUTOBOT_CYBERARK_PASSWORD", None), + } + } +} +``` +- `url` - (required) The URL to access to the CyberARK instance. _e.g. 'https://cyberark.example.local' (Without the slash '/' char at the end) +- `token` - (optional) Either token or username/password need to be set to access the CyberARK API. +- `username` - (optional) Either token or username/password need to be set to access the CyberARK API. +- `password` - (optional) Either token or username/password need to be set to access the CyberARK API. + ## Contributing Pull requests are welcomed and automatically built and tested against multiple version of Python and multiple version of Nautobot through GitHub Actions. diff --git a/nautobot_secrets_providers/providers/__init__.py b/nautobot_secrets_providers/providers/__init__.py index 3a0baf9..ea44b6d 100644 --- a/nautobot_secrets_providers/providers/__init__.py +++ b/nautobot_secrets_providers/providers/__init__.py @@ -3,10 +3,12 @@ from .aws import AWSSecretsManagerSecretsProvider, AWSSystemsManagerParameterStore from .hashicorp import HashiCorpVaultSecretsProvider from .delinea import ThycoticSecretServerSecretsProviderId, ThycoticSecretServerSecretsProviderPath +from .cyberark import CyberARKSecretsProvider __all__ = ( # type: ignore AWSSecretsManagerSecretsProvider, # pylint: disable=invalid-all-object AWSSystemsManagerParameterStore, # pylint: disable=invalid-all-object + CyberARKSecretsProvider, # pylint: disable=invalid-all-object HashiCorpVaultSecretsProvider, # pylint: disable=invalid-all-object ThycoticSecretServerSecretsProviderId, # pylint: disable=invalid-all-object ThycoticSecretServerSecretsProviderPath, # pylint: disable=invalid-all-object diff --git a/nautobot_secrets_providers/providers/cyberark.py b/nautobot_secrets_providers/providers/cyberark.py new file mode 100644 index 0000000..6acf36b --- /dev/null +++ b/nautobot_secrets_providers/providers/cyberark.py @@ -0,0 +1,104 @@ +import socket +from email.policy import default + +from django import forms +from django.conf import settings + +try: + import requests +except ImportError: + requests = None + +from nautobot.extras.secrets import SecretsProvider, exceptions +from nautobot.core.forms import BootstrapMixin + +__all__ = ("CyberARKSecretsProvider") + +class CyberARKSecretsProvider(SecretsProvider): + """ + A secrets provider for CyberARK AIM. + """ + + slug = "cyberark" + name = "CyberARK" + is_available = requests is None + + class ParametersForm(BootstrapMixin, forms.Form): + """ + Required parameters for CyberARK. + """ + + object = forms.CharField( + required = False, + help_text = "Object to requestin CyberARK (not required if username is given)" + ) + username = forms.CharField( + required = True if object.empty_value else False, + help_text = "Username to request in CyberARK (not required if objects is given)" + ) + object.required = True if username.empty_value else False + + @classmethod + def get_value_for_secret(cls, secret, obj=None, **kwargs): + """ + Return the value stored under the + + Args: + secret (_type_): _description_ + obj (_type_, optional): _description_. Defaults to None. + """ + + plugin_settings = settings.PLUGINS_CONFIG['nautobot_secret_providers'] + if 'cyberark' not in plugin_settings: + raise exceptions.SecretProviderError(secret, cls, "CyberARK is not configured") + + cyberark_settings = plugin_settings["cyberark"] + + if "url" not in cyberark_settings: + raise exceptions.SecretProviderError(secret, cls, "CyberARK url is not set in PLUGINS_CONFIG['nautobot_secret_providers']['cyberark']") + + parameters = secret.rendered_parameters(obj=obj) + try: + secret_username = parameters["username"] + secret_object = parameters["object"] + except KeyError as err: + msg = f"The secret parameter could not be retrieved for field {err}" + raise exceptions.SecretParametersError(secret, cls, msg) from err + + try: + if "token" in cyberark_settings: + token = cyberark_settings["token"] + else: + cyberark_settings["username"] + cyberark_settings["password"] + cyberark_settings["auth_method"] + except KeyError as err: + raise exceptions.SecretProviderError(secret, cls, f"Can't retrieve field {err} from PLUGINS_CONFIG['nautobot_secret_providers']['cyberark']") + + if "token" not in cyberark_settings: + response = requests.post( + f"{cyberark_settings['url']}/PasswordVault/API/auth/{cyberark_settings['auth_method']}/Logon", + verify = cyberark_settings['verifySsl'], # If needs to disable cert check only for CyberARK + data = { + "username": cyberark_settings["username"], + "password": cyberark_settings["password"] + } + ) + if response.status_code != 200: + raise exceptions.SecretProviderError(secret, cls, f"Can't get token: {response.status_code} {response.text}") + + token = response.text.replace('"', '') + + response = requests.get( + f"{cyberark_settings['url']}/PasswordVault/api/Accounts/?search={secret_username}", # This call can be very long to answer depending of the CyberARK instance setup + verify = cyberark_settings['verifySsl'], + headers = {"Authorization": token}, + ) + + if response.status_code != 200: + raise exceptions.SecretProviderError(secret, cls, f"Can't get value: {response.status_code} {response.text}") + return requests.post( + f"{cyberark_settings['url']}/PasswordVault/api/Accounts/{response.json()['value'][0]['id']}/Password/Retrieve", + verify = cyberark_settings["verifySsl"], + headers = { 'Authorization': token }, + ).text.replace('"', '') \ No newline at end of file diff --git a/nautobot_secrets_providers/templates/nautobot_secrets_providers/home.html b/nautobot_secrets_providers/templates/nautobot_secrets_providers/home.html index 10eafd3..31af8a2 100644 --- a/nautobot_secrets_providers/templates/nautobot_secrets_providers/home.html +++ b/nautobot_secrets_providers/templates/nautobot_secrets_providers/home.html @@ -46,6 +46,11 @@

{% block title %}Secrets Providers Home{% endblock %}

Domain Authorization
Access Token Authorization + + CyberARK + Password + Username/Password + diff --git a/pyproject.toml b/pyproject.toml index 65f9801..d604031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautobot-secrets-providers" -version = "2.0.1" +version = "2.0.2" description = "Nautobot Secrets Providers App" authors = ["Network to Code, LLC "] license = "Apache-2.0" @@ -60,11 +60,13 @@ all = [ "boto3", "hvac", "python-tss-sdk", + "requests" ] aws = ["boto3"] hashicorp = ["hvac"] nautobot = ["nautobot"] thycotic = ["python-tss-sdk"] +cyberark = ["requests"] [tool.black] line-length = 120