Closes #20492: Disable API token plaintext retrieval

This commit is contained in:
Jeremy Stretch 2025-10-14 14:57:37 -04:00
parent b7cc4c418b
commit 2bebfccf9b
8 changed files with 10 additions and 42 deletions

View File

@ -1,16 +1,5 @@
# Security & Authentication Parameters
## ALLOW_TOKEN_RETRIEVAL
Default: `False`
!!! note
The default value of this parameter changed from `True` to `False` in NetBox v4.3.0.
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token prior to its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
---
## ALLOWED_URL_SCHEMES
!!! tip "Dynamic Configuration Parameter"

View File

@ -80,7 +80,7 @@ Likewise, the site, rack, and device objects are located under the "DCIM" applic
The full hierarchy of available endpoints can be viewed by navigating to the API root in a web browser.
Each model generally has two views associated with it: a list view and a detail view. The list view is used to retrieve a list of multiple objects and to create new objects. The detail view is used to retrieve, update, or delete an single existing object. All objects are referenced by their numeric primary key (`id`).
Each model generally has two views associated with it: a list view and a detail view. The list view is used to retrieve a list of multiple objects and to create new objects. The detail view is used to retrieve, update, or delete a single existing object. All objects are referenced by their numeric primary key (`id`).
* `/api/dcim/devices/` - List existing devices or create a new device
* `/api/dcim/devices/123/` - Retrieve, update, or delete the device with ID 123
@ -655,6 +655,9 @@ The NetBox REST API primarily employs token-based authentication. For convenienc
A token is a secret, unique identifier mapped to a NetBox user account. Each user may have one or more tokens which he or she can use for authentication when making REST API requests. To create a token, navigate to the API tokens page under your user profile. When creating a token, NetBox will automatically populate a randomly-generated token value.
!!! note "Tokens cannot be retrieved once created"
Once a token has been created, its plaintext value cannot be retrieved. For this reason, you must take care to securely record the token locally immediately upon its creation. If a token plaintext is lost, it cannot be recovered: A new token must be created.
By default, all users can create and manage their own REST API tokens under the user control panel in the UI or via the REST API. This ability can be disabled by overriding the [`DEFAULT_PERMISSIONS`](../configuration/security.md#default_permissions) configuration parameter.
Additionally, a token can be set to expire at a specific time. This can be useful if an external client needs to be granted temporary access to NetBox.
@ -663,7 +666,7 @@ Additionally, a token can be set to expire at a specific time. This can be usefu
Beginning with NetBox v4.5, two versions of API token are supported, denoted as v1 and v2. Users are strongly encouraged to create only v2 tokens and to discontinue the use of v1 tokens. Support for v1 tokens will be removed in a future NetBox release.
v2 API tokens offer much stronger security. The token plaintext given at creation time is hashed together with a configured [cryptographic pepper](../configuration/required-parameters.md#api_token_peppers) to generate a unique checksum. This checksum is irreversible; the token plaintext is never stored on the server and thus cannot be retrieved.
v2 API tokens offer much stronger security. The token plaintext given at creation time is hashed together with a configured [cryptographic pepper](../configuration/required-parameters.md#api_token_peppers) to generate a unique checksum. This checksum is irreversible; the token plaintext is never stored on the server and thus cannot be retrieved even with database-level access.
#### Restricting Write Operations

View File

@ -91,9 +91,6 @@ ADMINS = [
# ('John Doe', 'jdoe@example.com'),
]
# Permit the retrieval of API tokens after their creation.
ALLOW_TOKEN_RETRIEVAL = False
# Enable any desired validators for local account passwords below. For a list of included validators, please see the
# Django documentation at https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation.
AUTH_PASSWORD_VALIDATORS = [

View File

@ -43,8 +43,6 @@ SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
DEFAULT_PERMISSIONS = {}
ALLOW_TOKEN_RETRIEVAL = True
API_TOKEN_PEPPERS = {
1: 'TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE-TEST-VALUE-DO-NOT-USE',
}

View File

@ -76,7 +76,6 @@ elif hasattr(configuration, 'DATABASE') and hasattr(configuration, 'DATABASES'):
# Set static config parameters
ADMINS = getattr(configuration, 'ADMINS', [])
ALLOW_TOKEN_RETRIEVAL = getattr(configuration, 'ALLOW_TOKEN_RETRIEVAL', False)
ALLOWED_HOSTS = getattr(configuration, 'ALLOWED_HOSTS') # Required
API_TOKEN_PEPPERS = getattr(configuration, 'API_TOKEN_PEPPERS', {})
AUTH_PASSWORD_VALIDATORS = getattr(configuration, 'AUTH_PASSWORD_VALIDATORS', [

View File

@ -20,14 +20,7 @@
{% if object.version == 1 %}
<tr>
<th scope="row">{% trans "Token" %}</th>
<td>
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
<span id="secret" class="font-monospace" data-secret="{{ object.plaintext }}">{{ object.plaintext }}</span>
<button type="button" class="btn btn-primary toggle-secret float-end" data-bs-toggle="button">{% trans "Show Secret" %}</button>
{% else %}
{{ object.partial }}
{% endif %}
</td>
<td>{{ object.partial }}</td>
</tr>
{% else %}
<tr>

View File

@ -1,7 +1,6 @@
import json
from django import forms
from django.conf import settings
from django.contrib.auth import password_validation
from django.contrib.postgres.forms import SimpleArrayField
from django.core.exceptions import FieldError
@ -115,7 +114,7 @@ class UserTokenForm(forms.ModelForm):
label=_('Token'),
help_text=_(
'Tokens must be at least 40 characters in length. <strong>Be sure to record your key</strong> prior to '
'submitting this form, as it may no longer be accessible once the token has been created.'
'submitting this form, as it will no longer be accessible once the token has been created.'
),
widget=forms.TextInput(
attrs={'data-clipboard': 'true'}
@ -148,11 +147,8 @@ class UserTokenForm(forms.ModelForm):
self.fields['version'].disabled = True
self.fields['user'].disabled = True
# Omit the key field when editing an existing token if token retrieval is not permitted
if self.instance.v1 and settings.ALLOW_TOKEN_RETRIEVAL:
self.initial['token'] = self.instance.plaintext
else:
del self.fields['token']
# Omit the key field when editing an existing Token
del self.fields['token']
# Generate an initial random key if none has been specified
elif self.instance._state.adding and not self.initial.get('token'):

View File

@ -11,13 +11,7 @@ __all__ = (
'UserTable',
)
TOKEN = """<samp><a href="{{ record.get_absolute_url }}" id="token_{{ record.pk }}">{{ record }}</a></samp>"""
COPY_BUTTON = """
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
{% copy_content record.pk prefix="token_" color="success" %}
{% endif %}
"""
TOKEN = """<samp><a href="{{ record.get_absolute_url }}">{{ record }}</a></samp>"""
class TokenTable(NetBoxTable):
@ -48,7 +42,6 @@ class TokenTable(NetBoxTable):
)
actions = columns.ActionsColumn(
actions=('edit', 'delete'),
extra_buttons=COPY_BUTTON
)
class Meta(NetBoxTable.Meta):