-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial work for adding 1Password support. * Implement tests and docs. * Update pyproject.toml Fix onepassword-sdk install. * Update Lock File * Use onepassword-sdk and fix imports * Create Change Fragment * Update docs/admin/providers/onepassword_setup.md Co-authored-by: Glenn Matthews <[email protected]> * Docs update * Update docs/admin/install.md Co-authored-by: Gary Snider <[email protected]> * Update docs/admin/providers/onepassword_setup.md Co-authored-by: Gary Snider <[email protected]> * Replace asyncio with django built-in async. * Ruff and Docs update. * Update onepassword_setup.md Reword * Address Feedback --------- Co-authored-by: Glenn Matthews <[email protected]> Co-authored-by: Gary Snider <[email protected]>
- Loading branch information
1 parent
96c9cf9
commit 88ad234
Showing
18 changed files
with
459 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Added 1Password as a Secrets Provider. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,5 +163,11 @@ | |
}, | ||
} | ||
}, | ||
"one_password": { | ||
"vaults": {}, | ||
"token": os.getenv( | ||
"OP_SERVICE_ACCOUNT_TOKEN", | ||
), | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# 1Password Vault | ||
|
||
Requires a minimum of Python3.9 | ||
|
||
## Prerequisites | ||
|
||
You must create a Service Account for the 1Password vault/vaults you are trying to access. You can follow the [Getting Started with Service Accounts](https://developer.1password.com/docs/service-accounts/get-started/) to assist with creating the Service Account. | ||
|
||
!!! note | ||
The Service Account token needs to have access to the Vault that it is configured for. Per 1Password policy "You can't grant a service account access to your built-in Personal, Private, or Employee vault, or your default Shared vault." | ||
|
||
## Configuration | ||
|
||
You must provide a mapping in `PLUGINS_CONFIG` within your `nautobot_config.py`, for example: | ||
|
||
```python | ||
PLUGINS_CONFIG = { | ||
"nautobot_secrets_providers": { | ||
"one_password": { | ||
"token": os.environ.get("OP_SERVICE_ACCOUNT_TOKEN"), | ||
"vaults": { | ||
"MyVault": { | ||
"token": os.environ.get("OP_SERVICE_ACCOUNT_TOKEN"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
``` | ||
|
||
- `token` - (required) The 1Password Service Account Token to be used globally when it is not specified by a vault. | ||
- `vaults` (required) Each 1Password Vault that is supported by this app will be listed inside this dictionary. | ||
- `<vault_name>` (required) The name of the vault needs to be placed as a key inside the `vaults` dictionary. | ||
- `token` (optional) The 1Password Service Account Token to be used by the above vault, if overriding the global `token`. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"""1Password Secrets Provider for Nautobot.""" | ||
|
||
from asgiref.sync import async_to_sync | ||
from django import forms | ||
from django.conf import settings | ||
from nautobot.core.forms import BootstrapMixin | ||
from nautobot.extras.secrets import SecretsProvider, exceptions | ||
|
||
try: | ||
from onepassword.client import Client | ||
except ImportError: | ||
Client = None | ||
|
||
from nautobot_secrets_providers import __version__ | ||
|
||
__all__ = ("OnePasswordSecretsProvider",) | ||
|
||
|
||
@async_to_sync | ||
async def get_secret_from_vault(vault, item, field, token, section=None): | ||
"""Get a secret from a 1Password vault. | ||
Args: | ||
vault (str): 1Password Vault where the secret is located. | ||
item (str): 1Password Item where the secret is located. | ||
field (str): 1Password secret field name. | ||
token (str): 1Password Service Account token. | ||
section (str, optional): 1Password Item Section for the secret. Defaults to None. | ||
Returns: | ||
(str): Value from the secret. | ||
""" | ||
client = await Client.authenticate( | ||
auth=token, integration_name="nautobot-secrets-providers", integration_version=__version__ | ||
) | ||
reference = f"op://{vault}/{item}/{f'{section}/' if section else ''}{field}" | ||
return await client.secrets.resolve(reference) | ||
|
||
|
||
def vault_choices(): | ||
"""Generate Choices for vault form field. | ||
Build a form option for each key in vaults. | ||
""" | ||
plugin_settings = settings.PLUGINS_CONFIG["nautobot_secrets_providers"] | ||
return [(key, key) for key in plugin_settings["one_password"]["vaults"].keys()] | ||
|
||
|
||
class OnePasswordSecretsProvider(SecretsProvider): | ||
"""A secrets provider for 1Password.""" | ||
|
||
slug = "one-password" | ||
name = "1Password Vault" | ||
is_available = Client is not None | ||
|
||
# TBD: Remove after pylint-nautobot bump | ||
# pylint: disable-next=nb-incorrect-base-class | ||
class ParametersForm(BootstrapMixin, forms.Form): | ||
"""Required parameters for HashiCorp Vault.""" | ||
|
||
vault = forms.ChoiceField( | ||
required=True, | ||
choices=vault_choices, | ||
help_text="1Password Vault to retrieve the secret from.", | ||
) | ||
item = forms.CharField( | ||
required=True, | ||
help_text="The item in 1Password.", | ||
) | ||
section = forms.CharField( | ||
required=False, | ||
help_text="The section where the field is a part of.", | ||
) | ||
field = forms.CharField( | ||
required=True, | ||
help_text="The field where the secret is located. Defaults to 'password'.", | ||
initial="password", | ||
) | ||
|
||
@classmethod | ||
def get_token(cls, secret, vault): | ||
"""Get the token for a vault.""" | ||
plugin_settings = settings.PLUGINS_CONFIG["nautobot_secrets_providers"] | ||
if "token" in plugin_settings["one_password"]["vaults"][vault]: | ||
return plugin_settings["one_password"]["vaults"][vault]["token"] | ||
try: | ||
return plugin_settings["one_password"]["token"] | ||
except KeyError as exc: | ||
raise exceptions.SecretProviderError(secret, cls, "1Password token is not configured!") from exc | ||
|
||
@classmethod | ||
def get_value_for_secret(cls, secret, obj=None, **kwargs): # pylint: disable=too-many-locals | ||
"""Get the value for a secret from 1Password.""" | ||
# This is only required for 1Password therefore not defined in | ||
# `required_settings` for the app config. | ||
plugin_settings = settings.PLUGINS_CONFIG["nautobot_secrets_providers"] | ||
if "one_password" not in plugin_settings: | ||
raise exceptions.SecretProviderError(secret, cls, "1Password is not configured!") | ||
|
||
parameters = secret.rendered_parameters(obj=obj) | ||
vault = parameters["vault"] | ||
|
||
return get_secret_from_vault( | ||
vault=vault, | ||
item=parameters["item"], | ||
field=parameters["field"], | ||
token=cls.get_token(secret, vault=vault), | ||
section=parameters.get("section", None), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.