Netbox-Secret

What is a Secret in Netbox

In Netbox, a secret means important information that needs to be stored securely. Furthermore, each secret can have a name parameter, which is sometimes used to store a username that is not encrypted.

Secret

A secret is always attached to a Device or a Virtual-Machine.

Secret Panel

Role in Netbox-Secret

A role is always assigned to a secret, the role is usually used to describe what it is used for. For example it can be used for Login credentials or SSH keys or even for some Routing secrets.

Interaction with Netbox-Secret

A helper NetboxSecret and several plugins have been developed to interact with Netbox-Secret. The helper is capable of:

  • Managing roles and secret

  • Handling the session towards Netbox-Secret

Here is the list of the plugins and their functionality:

  • generate_password: Retrieve or generate a password for the Netbox resource (either a VM or a Device)

  • create_secret_role: This plugin creates a new secret role

  • build_secret_reference: Generate from the netbox resource, the query needed to retrieve the netbox object

This query can be used with the Netbox-Secret helper to retrieve the information about the specified secret.

Session sharing

Logging in to Netbox-Secret is expensive: retrieving the session key triggers an RSA decryption on the Netbox server. To avoid paying this cost for every secret resolution, the secret references (netbox::create_secret_reference and netbox::create_secret_reference_v2) use a session shared by all the threads of the process (i.e. all the agents running on the same executor), via the get_shared_secret_helper function. The login happens at most once per url/RSA key/token combination, behind a lock, and the resulting session is reused for the whole lifetime of the process.

If the Netbox server can’t handle the load of many agents resolving secrets in parallel, the INMANTA_NETBOX_SERIALIZE_SECRET_REQUESTS environment variable can be set to a non-empty value to serialize all the requests made towards Netbox-Secret behind a lock. The variable is read when the shared session is created.

Dealing with secrets in the Resource and the Handler

We will first explain what is the abstract interface that handlers should implement in order to support a secret integration. Then, we will see what is happening when the resources are exported and when the handler is trying to retrieve credentials.

Basic abstraction

For the secret integration, it was decided to override methods instead of fields. Why? Because each module may have a different name convention for the variable. Furthermore, the meaning of the Credential values is also not consistent, some value might expect and environment variable, other will rely on the raw value. Therefore, it’s not possible to have a generic approach with these different names. Besides it’s not desirable to have breaking changes in these modules, just to have some consistency across the modules.

The other solution was to override methods. As opposed to variables, methods can be easily refactored without having any impact for the customer (no breaking changes). So, a convention was decided to share abstract common methods: usually a handler, would need to retrieve some credentials (username/password), it would now retrieve these credentials from methods respecting the following abstraction.

    def some_meaninful_method_name(self, ctx: handler.HandlerContext, resource: Resource) -> str:
        pass

Each handler wanting to interact with the secret integration has been updated to follow this convention. In MPN, usually with a get_username and a get_password methods but the name of the methods does not need to be consistent across modules.

Resource - Export phase

In MPN, we override the ResourceHandler.pre method to check if a secret_reference field exists in the resource. If it is the case, we check which method needs to be overriden by the secret:

Two methods can be overriden:

  • The one contained in the method_to_override_secret variable, usually the password

  • The one contained in the method_to_override_name_secret variable, usually the username. This override is optional, so if nothing is set in the entry, this override will be skipped.

Handler - Retrieval phase

The new methods will retrieve the secret from Netbox-Secret, to do so, the agent will need to have the RSA key pair. Depending on the information that needs to be retrieved, the handler will get the secret’s name or the secret plaintext.

Retrieval of secrets

If a resource intends to rely on Netbox-Secret, it needs to possess a secret_reference field to make aware the handler that an integration with secret is possible. Furthermore, only the resource “knows” which Netbox resource the Netbox-Secret helper would need to resolve the secret. Therefore, the responsibility for the secret integration is given to the resource that needs it.

The field will contain the query that we mentioned earlier. This query contains two parts:

  1. The part to resolve the Netbox resource (vertical yellow line on the left)

  2. The part to resolve the secret linked to this Netbox Resource (vertical gray line on the left)

Secret Reference

We will explain in the next sections how to resolve and retrieve these different information.

Resolve a Netbox resource

The Netbox object will be resolved by this class:

Netbox Resolver

We use two pieces of information to resolve Netbox objects:

  • The URL to retrieve the Netbox object

  • A query containing information used to identity the Netbox Object

    • A query can contain sub-query, e.g. if a Netbox object depends on the resolution of another Netbox object

In the previous example, we have a direct reference because the query contains a string that represents the name of the object we want to find.

How do we identify a secret

Given that we cannot rely on the secret’s name, we must adapt the search depending on the provided input. Two parameters are always used during the search: a Netbox resource (where the secret is / will be stored) and the role of the secret.

Netbox Resolver

We mainly handle two cases:

  • Either the secret’s name is provided, then we rely also on the name of the secret. For example, if a Netbox resource possesses multiple secrets with the same role, the name is used to distinguish them.

  • In the other case, we only rely on the two mandatory information: the Netbox resource and the role of the secret

Global overview

Here is a global overview on how this secret integration works with Netbox:

Global overview

Initial setup

When no public key is registered for the user in Netbox-Secrets yet, the helper bootstraps the setup on its first login: it generates a local RSA key pair when none exists (at that point a freshly generated pair can not conflict with anything) and registers the public key as the user’s user key. On netbox-secrets >= 3.0 (netbox >= 4.5) the registration uses the user-keys Rest API endpoint; on older releases that endpoint is broken (see https://github.com/Onemind-Services-LLC/netbox-secrets/issues/89) and an HTML session is used instead, which requires the NETBOX_USER and NETBOX_PASSWORD environment variables.

Once a public key is registered, the helper never modifies it:

  • a missing local key pair makes the helper raise NoLocalKeyPairException instead of generating one — a freshly generated pair could never match the registered key, which would permanently break secret resolution

  • key rotation is not supported, see below

Key pair location

The directory holding the key pair (private_key.pem and public_key.pem) is resolved with the following precedence (see get_rsa_key_dir):

  1. The rsa_key_dir attribute of the netbox::Credentials entity (or the rsa_key_dir argument of netbox::create_api_reference). This value travels inside the exported secret references, so it is used regardless of which process (compiler, executor, …) resolves the secret. Prefer this option.

  2. The INMANTA_NETBOX_RSA_KEY_DIR environment variable, read by the process resolving the secret.

  3. The rsa_keys directory inside the inmanta state dir of the process resolving the secret. This legacy fallback is fragile: the compiler and the executors do not share the same state dir, and the state dir layout can change between inmanta-core versions.

General information

  • If a netbox object storing a secret is deleted, the secret will not be deleted. The secret will be completely broken given that its dependency does not exist anymore. The only possible operation is to remove the problematic secret.

Rotating keys

If the last RSA key pair were to be changed, some precautions should be taken in order to avoid ending with a broken netbox instance.

  • Any existing session keys should be removed

  • Every secret created with this setup should be deleted