Closed chadell closed 1 year ago
@mundruid is currently working on it
We have a proposed implementation that has gotten extensive testing for the past 1.5 months by client.
get_secret
jinja Nautobot filter that will be added in helper.py
The signature of the function is shown below:
@library.filter()
@register.filter()
def get_secret(
user: User,
obj_id: str,
obj_type: str,
secret_type: str,
secret_access_type: Optional[str] = SecretsGroupAccessTypeChoices.TYPE_GENERIC,
) -> Optional[str]:
"""Gets the secrets attached to an object based on an ORM relationship.
We assume that there is only one secret group corresponding to a model.
Args:
user (User): User object that performs API call to render push template with secrets.
obj_id (str): Primary key returned for the specific object in the template. The id needs to be part of the GraphQL query.
obj_type (str): Type of the object in the form of <module_name>.<model_name>, for example: circuits.Circuit
secret_type (str): Type of secret, such as "username", "password", "token", "secret", or "key".
secret_access_type (Optional[str], optional): Type of secret such as "Generic", "gNMI", "HTTP(S)". Defaults to "Generic".
.. rubric:: Example Jinja get_secret filter usage
.. highlight:: jinja
.. code-block:: jinja
password {{ id | get_secret("dcim.Device", "password") | encrypt_type5 }}
ppp pap sent-username {{ interface["connected_circuit_termination"]["circuit"]["id"] | get_secret("circuits.Circuit", "username") }} password {{ interface["connected_circuit_termination"]["circuit"]["id"] | get_secret("circuits.Circuit", "password") | encrypt_type7 }}
Returns:
Optional[str] : Secret value. None if there is no match. An error string if there is an error.
"""
get_secret
filter should extract a secret based on the Nautobot object that has a relationship with the specific secrets group. Therefore, the user needs to provide the type of object using the arguments object_id
and object_type
in the function signature above. The object id will be returned by a Nautobot GraphQL query. The object_type
has to be a valid Nautobot class, ex. dcim.Device
get_secret
should specify the type of secret, ex. password. This left is up to the user that is using the filter.get_secret
does not need to provide an encrypted secret. That is the responsibility of the user to place the proper encryption algorithm or a hashed secret in their vault.get_secret
needs to provide authentication capability so that the secrets are rendered in a need to know basis only by authorized users. Note from the usage examples above, that the user
argument is not provided when the user is using the get_secret
filter in their template to avoid fake authorization. A wrapper of the get_secret
is defined in the config_intended.py
nornir play where the user
argument is injected based on who is running the job that is rendering the configuration:
def user_gets_secret(*args, **kwargs):
return get_secret(request.user, *args, **kwargs)
This way the get_secret
filter and additional API endpoint and view ensure security and utility.
@itdependsnetworks @jeffkala @chadell I look forward to your comments on this implementation.
@mundruid to explain step 5 properly, you should compare what is Intended
, within Golden Config, vs Candidate
, and why the second is the one that should use the get_secrets
filter.
I would also use an example of its usage, in the Jinja template. It would help to understand why we needed this filter. Also, to give context, the idea behind it is so give Golden Config with config provisioning/remediation capabilities (this links to my first point).
Does it make sense to have this filter in Nautobot Core?
For point 6, I am not sure I understand.
Regarding Nautobot Core point. We had a sync with @glennmatthews , and evaluating the potential risk when using the filter to leak secrets, and its usage (the only identified use-case so far it's this plugin), we concluded to implement it here until we find out another use-case (another plugin) that could benefit from it, and then consider importing to Core.
@itdependsnetworks, I think @chadell covered the point about Nautobot core: the filter seems GC specific for now so we suggest to keep int in the plugin and if we find another application then put into core.
Also, there is a set of two different configurations that I am referencing that Christian has already mentioned:
intended
: that does not include secrets and can be used for compliance but cannot be used for provisioning.candidate
: that includes secrets, cannot be used for compliance, and can be used for provisioning.Regarding the question: "What are you comparing when you have this split capability." See my point above. For compliance, we compare backup config with intended config.
Regarding the questions: "Rendering on the fly when?
This seems like it would be a blocking api request which would mean it should be async, but if not stored, how would that work?"
There is no blocking, we are rendering when the user requests it, i.e., running the task `generate_config` with the filter rendering the secrets.
Regarding the question: "What is the goal? " The goal is to render a candidate configuration with secrets that can be applied to a router using an orchestrator or a config provisioning job in GC. The second goal is to provide an API endpoint to retrieve this candidate configuration with a simple call.
I hope this clarifies my points.
For within core or not, thanks for the clarification.
Regarding the question: "What are you comparing when you have this split capability." See my point above. For compliance, we compare backup config with intended config.
I read this as backup will have secrets removed and intended will not have secrets, makes sense, thanks for clarification.
There is no blocking, we are rendering when the user requests it, i.e., running the task
generate_config
with the filter rendering the secrets.
The first part of this sentence "There is no blocking" seems to conflict with "running the task generate_config
". I do not understand how a task would not be a job and/or blocking?
Regarding the question: "What is the goal? " The goal is to render a candidate configuration with secrets that can be applied to a router using an orchestrator or a config provisioning job in GC. The second goal is to provide an API endpoint to retrieve this candidate configuration with a simple call.
ok, kinda get it. I think we need to speak to figure out between #255 #256 and this issue how they all live together. I am inclined to have a "config_prepare" or similar function, that allows you to modify the config before pushing. That would be there for remediation, secrets replacement (as you are describing), filtering configs, etc..
Completed in #339
Proposed Functionality
Related to #221 , this issue is specially focused on creating a Jinja filter that could render secrets from Nautobot, either for Secrets Groups FK from an object (for instance
Device
), but also for customRelationship
to aSecretsGroup
. Obviously, taking into account the permissions of the user rendering the secret to have Read access to the SecretGroup.Use Case
This will allow to render configurations with secrets, which are not available via Graphql, directly in Jinja templates.