Skip to content

Commit 9f3f219

Browse files
authored
Merge pull request #86 from netboxlabs/feat-authn-oauth2
feat: implement OAuth2 authentication and permissions
2 parents 0d3522a + 6bd1215 commit 9f3f219

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+506
-1093
lines changed

README.md

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ at [https://netboxlabs.com/blog/introducing-diode-streamlining-data-ingestion-in
1616
|:--------------:|:--------------:|
1717
| >= 3.7.2 | 0.1.0 |
1818
| >= 4.1.0 | 0.4.0 |
19+
| >= 4.2.3 | 1.0.0 |
1920

2021
## Installation
2122

@@ -46,32 +47,18 @@ Also in your `configuration.py` file, in order to customise the plugin settings,
4647
```python
4748
PLUGINS_CONFIG = {
4849
"netbox_diode_plugin": {
49-
# Auto-provision users for Diode plugin
50-
"auto_provision_users": False,
51-
5250
# Diode gRPC target for communication with Diode server
5351
"diode_target_override": "grpc://localhost:8080/diode",
5452

55-
# User allowed for Diode to NetBox communication
56-
"diode_to_netbox_username": "diode-to-netbox",
57-
58-
# User allowed for NetBox to Diode communication
59-
"netbox_to_diode_username": "netbox-to-diode",
60-
61-
# User allowed for data ingestion
62-
"diode_username": "diode-ingestion",
53+
# Username associated with changes applied via plugin
54+
"diode_username": "diode",
6355
},
6456
}
6557
```
6658

6759
Note: Once you customise usernames with PLUGINS_CONFIG during first installation, you should not change or remove them
6860
later on. Doing so will cause the plugin to stop working properly.
6961

70-
`auto_provision_users` is a boolean flag (default: `False`) that determines whether the plugin should automatically
71-
create the users during
72-
migration. If set to `False`, you will need to provision Diode users with their API keys manually via the plugin's setup
73-
page in the NetBox UI.
74-
7562
Restart NetBox services to load the plugin:
7663

7764
```
@@ -89,28 +76,6 @@ cd /opt/netbox
8976
source venv/bin/activate
9077
```
9178

92-
Three API keys will be needed (these are random 40 character long alphanumeric strings). They can be generated and set
93-
to the appropriate environment variables with the following commands:
94-
95-
```shell
96-
# API key for the Diode service to interact with NetBox
97-
export DIODE_TO_NETBOX_API_KEY=$(head -c20 </dev/urandom|xxd -p); env | grep DIODE_TO_NETBOX_API_KEY
98-
# API key for the NetBox service to interact with Diode
99-
export NETBOX_TO_DIODE_API_KEY=$(head -c20 </dev/urandom|xxd -p); env | grep NETBOX_TO_DIODE_API_KEY
100-
# API key for Diode SDKs to ingest data into Diode
101-
export DIODE_API_KEY=$(head -c20 </dev/urandom|xxd -p); env | grep DIODE_API_KEY
102-
```
103-
104-
**Note:** store these API key strings in a safe place as they will be needed later to configure the Diode server.
105-
106-
If you don't set these environment variables, the plugin will generate random API keys for you either during the
107-
migration process (with `auto_provision_users` set to `True`) or when you manually create the users in the plugin's
108-
setup page in the NetBox UI.
109-
110-
It's important to note that environment variables with API keys should be populated in the Diode server's
111-
environment variables (see [docs](https://github.com/netboxlabs/diode/tree/develop/diode-server#running-the-diode-server))
112-
as well to ensure proper communication between the Diode SDK, Diode server and the NetBox plugin.
113-
11479
Run migrations to create all necessary resources:
11580

11681
```shell

docker/netbox/env/netbox.env

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,5 @@ SUPERUSER_NAME=admin
3737
SUPERUSER_PASSWORD=admin
3838
WEBHOOKS_ENABLED=true
3939
RELOAD_NETBOX_ON_DIODE_PLUGIN_CHANGE=false
40-
DIODE_TO_NETBOX_API_KEY=1368dbad13e418d5a443d93cf255edde03a2a754
41-
NETBOX_TO_DIODE_API_KEY=1e99338b8cab5fc637bc55f390bda1446f619c42
42-
DIODE_API_KEY=5a52c45ee8231156cb620d193b0291912dd15433
4340
BASE_PATH=netbox/
44-
DEBUG=True
41+
DEBUG=False

docker/netbox/plugins_dev.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
PLUGINS_CONFIG = {
1313
"netbox_diode_plugin": {
14-
"auto_provision_users": True,
1514
# Diode gRPC target for communication with Diode server
1615
"diode_target_override": "grpc://host.docker.internal:8080/diode",
16+
17+
# Username associated with changes applied via plugin
18+
"diode_username": "diode",
1719
}
1820
}

netbox-plugin.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: 0.1
22
package_name: netboxlabs-diode-netbox-plugin
33
compatibility:
4-
- release: 0.7.0
4+
- release: 1.0.0
55
netbox_min: 4.2.3
66
netbox_max: 4.2.3
77
- release: 0.6.0

netbox_diode_plugin/__init__.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python
2-
# Copyright 2024 NetBox Labs Inc
2+
# Copyright 2025 NetBox Labs, Inc.
33
"""Diode NetBox Plugin."""
44

55
from netbox.plugins import PluginConfig
@@ -17,16 +17,11 @@ class NetBoxDiodePluginConfig(PluginConfig):
1717
base_url = "diode"
1818
min_version = "4.2.3"
1919
default_settings = {
20-
# Auto-provision users for Diode plugin
21-
"auto_provision_users": False,
2220
# Default Diode gRPC target for communication with Diode server
2321
"diode_target": "grpc://localhost:8080/diode",
24-
# User allowed for Diode to NetBox communication
25-
"diode_to_netbox_username": "diode-to-netbox",
26-
# User allowed for NetBox to Diode communication
27-
"netbox_to_diode_username": "netbox-to-diode",
28-
# User allowed for data ingestion
29-
"diode_username": "diode-ingestion",
22+
23+
# Default username associated with changes applied via plugin
24+
"diode_username": "diode",
3025
}
3126

3227

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/usr/bin/env python
2-
# Copyright 2024 NetBox Labs Inc
2+
# Copyright 2025 NetBox Labs, Inc.
33
"""Diode NetBox Plugin - API."""

netbox_diode_plugin/api/applier.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
#!/usr/bin/env python
2-
# Copyright 2024 NetBox Labs Inc
2+
# Copyright 2025 NetBox Labs, Inc.
33
"""Diode NetBox Plugin - API - Applier."""
44

55

66
import logging
77

8-
from django.apps import apps
9-
from django.contrib.contenttypes.models import ContentType
108
from django.core.exceptions import ObjectDoesNotExist
119
from django.db import models
1210
from django.db.utils import IntegrityError
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python
2+
# Copyright 2025 NetBox Labs, Inc.
3+
"""Diode NetBox Plugin - API Authentication."""
4+
5+
import hashlib
6+
import logging
7+
from types import SimpleNamespace
8+
9+
import requests
10+
from django.core.cache import cache
11+
from rest_framework.authentication import BaseAuthentication
12+
from rest_framework.exceptions import AuthenticationFailed
13+
14+
from netbox_diode_plugin.plugin_config import (
15+
get_diode_auth_introspect_url,
16+
get_diode_user,
17+
)
18+
19+
logger = logging.getLogger("netbox.diode_data")
20+
21+
22+
class DiodeOAuth2Authentication(BaseAuthentication):
23+
"""Diode OAuth2 Client Credentials Authentication."""
24+
25+
def authenticate(self, request):
26+
"""Authenticate the request and return the user info."""
27+
auth_header = request.headers.get("Authorization", "")
28+
if not auth_header.startswith("Bearer "):
29+
return None
30+
31+
token = auth_header[7:].strip()
32+
33+
diode_user = self._introspect_token(token)
34+
if not diode_user:
35+
raise AuthenticationFailed("Invalid token")
36+
37+
request.user = diode_user.user
38+
request.token_scopes = diode_user.token_scopes
39+
request.token_data = diode_user.token_data
40+
41+
return (diode_user.user, None)
42+
43+
def _introspect_token(self, token: str):
44+
"""Introspect the token and return the client info."""
45+
hash_token = hashlib.sha256(token.encode()).hexdigest()
46+
cache_key = f"diode:oauth2:introspect:{hash_token}"
47+
cached_user = cache.get(cache_key)
48+
if cached_user:
49+
return cached_user
50+
51+
introspect_url = get_diode_auth_introspect_url()
52+
53+
if not introspect_url:
54+
logger.error("Diode Auth introspect URL is not configured")
55+
return None
56+
57+
try:
58+
response = requests.post(
59+
introspect_url, headers={"Authorization": f"Bearer {token}"}, timeout=5
60+
)
61+
response.raise_for_status()
62+
data = response.json()
63+
except Exception as e:
64+
logger.error(f"Diode Auth token introspection failed: {e}")
65+
return None
66+
67+
if data.get("active"):
68+
diode_user = SimpleNamespace(
69+
user=get_diode_user(),
70+
token_scopes=data.get("scope", "").split(),
71+
token_data=data,
72+
)
73+
74+
expires_in = (
75+
data.get("exp") - data.get("iat")
76+
if "exp" in data and "iat" in data
77+
else 300
78+
)
79+
cache.set(cache_key, diode_user, timeout=expires_in)
80+
return diode_user
81+
82+
return None

netbox_diode_plugin/api/differ.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44

55
import copy
66
import datetime
7-
import decimal
87
import logging
98

10-
import netaddr
119
from django.contrib.contenttypes.models import ContentType
12-
from django.db.backends.postgresql.psycopg_any import NumericRange
13-
from netaddr.eui import EUI
1410
from rest_framework import serializers
1511
from utilities.data import shallow_compare_dict
1612

netbox_diode_plugin/api/matcher.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
#!/usr/bin/env python
2-
# Copyright 2024 NetBox Labs Inc
2+
# Copyright 2025 NetBox Labs, Inc.
33
"""Diode NetBox Plugin - API - Object matching utilities."""
44

5-
import copy
65
import logging
76
from dataclasses import dataclass
87
from functools import cache, lru_cache
98
from typing import Type
109

1110
import netaddr
12-
from core.models import ObjectType as NetBoxType
13-
from django.conf import settings
1411
from django.contrib.contenttypes.fields import ContentType
1512
from django.core.exceptions import FieldDoesNotExist
1613
from django.db import models
@@ -20,7 +17,7 @@
2017
from django.db.models.query_utils import Q
2118
from extras.models.customfields import CustomField
2219

23-
from .common import _TRACE, AutoSlug, UnresolvedReference
20+
from .common import _TRACE, UnresolvedReference
2421
from .plugin_utils import content_type_id, get_object_type, get_object_type_model
2522

2623
logger = logging.getLogger(__name__)

0 commit comments

Comments
 (0)