Skip to content

Commit

Permalink
Merge pull request #118 from Onemind-Services-LLC/dev
Browse files Browse the repository at this point in the history
Release v1.9.1
  • Loading branch information
abhi1693 authored Jan 1, 2024
2 parents d9fbd96 + 9929428 commit b1abd4c
Show file tree
Hide file tree
Showing 18 changed files with 158 additions and 101 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ jobs:

- id: docker-test
name: Test the image
run: ./test.sh snapshot
run: ./test.sh
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@

## [Unreleased](https://github.com/Onemind-Services-LLC/netbox-secrets/tree/HEAD)

[Full Changelog](https://github.com/Onemind-Services-LLC/netbox-secrets/compare/v1.8.5...HEAD)
[Full Changelog](https://github.com/Onemind-Services-LLC/netbox-secrets/compare/v1.9.0...HEAD)

**Closed issues:**

- \[Bug\]: Unable to Activate Selected User Keys in Admin Panel [\#106](https://github.com/Onemind-Services-LLC/netbox-secrets/issues/106)
- \[Bug\]: Code key length mistake 4097 instead of 4096 [\#92](https://github.com/Onemind-Services-LLC/netbox-secrets/issues/92)
- \[Docs\]: API Pulling session Key [\#83](https://github.com/Onemind-Services-LLC/netbox-secrets/issues/83)

## [v1.9.0](https://github.com/Onemind-Services-LLC/netbox-secrets/tree/v1.9.0) (2023-09-07)

[Full Changelog](https://github.com/Onemind-Services-LLC/netbox-secrets/compare/v1.8.5...v1.9.0)

**Closed issues:**

Expand All @@ -17,6 +27,8 @@

**Merged pull requests:**

- Prepare for release [\#98](https://github.com/Onemind-Services-LLC/netbox-secrets/pull/98) ([abhi1693](https://github.com/abhi1693))
- Add support for NetBox v3.6 [\#97](https://github.com/Onemind-Services-LLC/netbox-secrets/pull/97) ([kprince28](https://github.com/kprince28))
- Bump word-wrap from 1.2.3 to 1.2.4 in /netbox\_secrets/project-static [\#86](https://github.com/Onemind-Services-LLC/netbox-secrets/pull/86) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump semver from 6.3.0 to 6.3.1 in /netbox\_secrets/project-static [\#84](https://github.com/Onemind-Services-LLC/netbox-secrets/pull/84) ([dependabot[bot]](https://github.com/apps/dependabot))

Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ services:
build:
dockerfile: Dockerfile
context: .
args:
NETBOX_VARIANT: ${NETBOX_VARIANT}
depends_on:
- postgres
- redis
Expand Down
35 changes: 30 additions & 5 deletions docs/rest-api/working-with-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,25 @@ steps are needed to encrypt or decrypt secret data.

In order to encrypt or decrypt secret data, a session key must be attached to the API request. To generate a session key,
send an authenticated request to the `/api/plugins/secrets/session-keys/` endpoint with the private RSA key which
matches your [UserKey](../models/userkey.md). The private key must be POSTed with the name `private_key`.
matches your [UserKey](../models/userkey.md). Place the private RSA key in a json file.

```no-highlight
$ curl -X POST http://netbox/api/plugins/secrets/session-keys/ \
-H "Authorization: Token $TOKEN" \
-H "Accept: application/json; indent=4" \
--data-urlencode "private_key@<filename>"
-H "Content-Type: application/json" \
--data @<filename>
```

```json
{
"pk": 7,
"id": 7,
"url": "http://172.16.14.63:8000/api/plugins/secrets/session-keys/7/",
"url": "http://netbox/api/plugins/secrets/session-keys/7/",
"display": "admin (RSA)",
"userkey": {
"id": 1,
"url": "http://172.16.14.63:8000/api/plugins/secrets/user-keys/1/",
"url": "http://netbox/api/plugins/secrets/user-keys/1/",
"display": "admin"
},
"session_key": "4H8MCOl98qom7Ug5fQTzsFcH600SRWxe7KlUyIYxJ+A=",
Expand All @@ -34,11 +35,35 @@ $ curl -X POST http://netbox/api/plugins/secrets/session-keys/ \

!!! note
To read the private key from a file, use the convention above. Alternatively, the private key can be read from an
environment variable using `--data-urlencode "private_key=$PRIVATE_KEY"`.
environment variable using `--data "{\"private_key\": \"$PRIVATEKEY\"}"`.

Use the following CLI command to convert your PEM RSA key to json:

```no-highlight
jq -sR . <filename>
```

The request uses the provided private key to unlock your stored copy of the master key and generate a temporary
session key, which can be attached in the `X-Session-Key` header of future API requests.

### Depracated!

If you still want to use `application/x-www-form-urlencoded` you can use the **depracated** API endpoint
`http://netbox/api/plugins/secrets/get-session-key/`.

```no-highlight
curl -X POST https://netbox-test.tugraz.at/api/plugins/secrets/get-session-key/ \
-H "Authorization: Token $TOKEN" \
-H "Accept: application/json; indent=4" \
--data-urlencode "private_key@<filename>"
```

```json
{
"session_key": "4H8MCOl98qom7Ug5fQTzsFcH600SRWxe7KlUyIYxJ+A="
}
```

## Retrieving Secrets

A session key is not needed to retrieve unencrypted secrets: The secret is returned like any normal object with its
Expand Down
33 changes: 20 additions & 13 deletions netbox_secrets/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,46 @@ class UserKeyAdmin(admin.ModelAdmin):
fields = ['user', 'public_key', 'is_active', 'last_updated']
readonly_fields = ['user', 'is_active', 'last_updated']

def has_add_permission(self, request):
# Don't allow a user to create a new public key directly.
return False

def has_delete_permission(self, request, obj=None):
# Don't allow a user to delete a public key directly.
return False

def get_readonly_fields(self, request, obj=None):
# Don't allow a user to modify an existing public key directly.
if obj and obj.public_key:
return ['public_key'] + self.readonly_fields
return self.readonly_fields

def get_actions(self, request):
# Bulk deletion is disabled at the manager level, so remove the action from the admin site for this model.
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
if not request.user.has_perm('secrets.change_userkey'):
del actions['activate_selected']
return actions

@admin.action(description='Activate selected public keys', permissions=['change'])
def activate_selected(self, request, queryset):
"""
Enable bulk activation of UserKeys
"""
try:
my_userkey = UserKey.objects.get(user=request.user)
except UserKey.DoesNotExist:
messages.error(request, "You do not have an active User Key.")
messages.error(request, "You do not have a User Key.")
return redirect('admin:netbox_secrets_userkey_changelist')

if not my_userkey.is_active():
messages.error(request, "Your User Key is not active.")
return redirect('admin:netbox_secrets_userkey_changelist')

if 'activate' in request.POST:
form = ActivateUserKeyForm(request.POST)
if form.is_valid():
master_key = my_userkey.get_master_key(form.cleaned_data['secret_key'])
if master_key is not None:
for uk in form.cleaned_data['_selected_action']:
for uk in form.cleaned_data[ACTION_CHECKBOX_NAME]:
uk.activate(master_key)
messages.success(
request,
"Successfully activated {} user keys.".format(len(form.cleaned_data[ACTION_CHECKBOX_NAME])),
)
return redirect('admin:netbox_secrets_userkey_changelist')
else:
messages.error(
Expand All @@ -53,7 +61,7 @@ def activate_selected(self, request, queryset):
extra_tags='error',
)
else:
form = ActivateUserKeyForm(initial={'_selected_action': request.POST.getlist(ACTION_CHECKBOX_NAME)})
form = ActivateUserKeyForm(initial={ACTION_CHECKBOX_NAME: request.POST.getlist(ACTION_CHECKBOX_NAME)})

return render(
request,
Expand All @@ -63,4 +71,3 @@ def activate_selected(self, request, queryset):
},
)

activate_selected.short_description = "Activate selected user keys"
2 changes: 1 addition & 1 deletion netbox_secrets/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def list(self, request):
except ValueError:
key_size = public_key_size

if key_size not in range(2048, 4097, 256):
if key_size not in range(2048, 8193, 256):
key_size = public_key_size

# Export RSA private and public keys in PEM format
Expand Down
33 changes: 3 additions & 30 deletions netbox_secrets/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@


class SecretRoleFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
name = MultiValueCharFilter(lookup_expr='iexact')

class Meta:
model = SecretRole
fields = ['id', 'name', 'slug', 'description', 'comments']
fields = ['id', 'slug', 'description', 'comments']

def search(self, queryset, name, value):
if not value.strip():
Expand All @@ -43,13 +39,6 @@ def search(self, queryset, name, value):
if plugin_settings.get('enable_contacts', False):

class SecretFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)

name = MultiValueCharFilter(lookup_expr='iexact')

role_id = django_filters.ModelMultipleChoiceFilter(
queryset=SecretRole.objects.all(),
label='Role (ID)',
Expand Down Expand Up @@ -78,12 +67,6 @@ class Meta:
model = Secret
fields = [
'id',
'assigned_object_type',
'assigned_object_type_id',
'assigned_object_id',
'role_id',
'role',
'name',
'contact',
'description',
'comments',
Expand All @@ -103,13 +86,9 @@ def search(self, queryset, name, value):
else:

class SecretFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
name = MultiValueCharFilter(
lookup_expr='iexact'
)

name = MultiValueCharFilter(lookup_expr='iexact')

role_id = django_filters.ModelMultipleChoiceFilter(
queryset=SecretRole.objects.all(),
label='Role (ID)',
Expand All @@ -132,12 +111,6 @@ class Meta:
model = Secret
fields = [
'id',
'assigned_object_type',
'assigned_object_type_id',
'assigned_object_id',
'role_id',
'role',
'name',
'_object_repr',
'description',
'comments',
Expand Down
15 changes: 12 additions & 3 deletions netbox_secrets/forms/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,28 @@

class SecretRoleFilterForm(NetBoxModelFilterSetForm):
model = SecretRole
q = forms.CharField(required=False, label=_('Search'))
id = DynamicModelMultipleChoiceField(queryset=SecretRole.objects.all(), required=False, label=_('Roles'))
fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Secret Role', ('id',))
)
id = DynamicModelMultipleChoiceField(queryset=SecretRole.objects.all(), required=False, label=_('Roles Name'))
tag = TagFilterField(model)


class SecretFilterForm(NetBoxModelFilterSetForm):
model = Secret

fieldsets = (
(None, ('q', 'filter_id', 'tag')),
('Secret', ('id',)),
('Attributes', ('role_id', 'assigned_object_type_id')),
)

q = forms.CharField(required=False, label=_('Search'))
id = DynamicModelMultipleChoiceField(
queryset=Secret.objects.all(),
required=False,
label=_('Name')
)
assigned_object_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(SECRET_ASSIGNABLE_MODELS),
required=False,
Expand Down
4 changes: 2 additions & 2 deletions netbox_secrets/models/secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ def clean(self):
},
)
# We can't use keys bigger than our master_key_cipher field can hold
if pubkey_length > 4096:
if pubkey_length > 8192:
raise ValidationError(
{
'public_key': "Public key size ({}) is too large. Maximum key size is 4096 bits.".format(
'public_key': "Public key size ({}) is too large. Maximum key size is 8192 bits.".format(
pubkey_length,
),
},
Expand Down
16 changes: 14 additions & 2 deletions netbox_secrets/navigation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from extras.plugins import PluginMenuButton, PluginMenuItem
from django.conf import settings
from extras.plugins import PluginMenuButton, PluginMenuItem, PluginMenu
from utilities.choices import ButtonColorChoices

menu_items = (
plugins_settings = settings.PLUGINS_CONFIG.get('netbox_secrets')

menu_buttons = (
PluginMenuItem(
link_text="User Key",
link="plugins:netbox_secrets:userkey",
Expand Down Expand Up @@ -34,3 +37,12 @@
permissions=["netbox_secrets.view_secret"],
),
)

if plugins_settings.get('top_level_menu'):
menu = PluginMenu(
label='Secrets',
groups=(('Secrets', menu_buttons),),
icon_class='mdi mdi-eye-closed',
)
else:
menu_items = menu_buttons
2 changes: 1 addition & 1 deletion netbox_secrets/project-static/src/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function initGenerateKeyPair() {
function toggleSecretButtons(id: string, action: 'lock' | 'unlock') {
const unlockButton = document.querySelector(`button.unlock-secret[secret-id='${id}']`);
const lockButton = document.querySelector(`button.lock-secret[secret-id='${id}']`);
const copyButton = document.querySelector(`button.copy-secret[secret-id='${id}']`);
const copyButton = document.querySelector(`span[secret-id='${id}']`);
// If we're unlocking, hide the unlock button. Otherwise, show it.
if (unlockButton !== null) {
if (action === 'unlock') unlockButton.classList.add('d-none');
Expand Down
Loading

0 comments on commit b1abd4c

Please sign in to comment.