diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..6af69fa
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,214 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+**/__pycache__/
+/netbox_secrets.egg-info
diff --git a/.github/configuration.testing.py b/.github/configuration.testing.py
deleted file mode 100644
index 80e563d..0000000
--- a/.github/configuration.testing.py
+++ /dev/null
@@ -1,38 +0,0 @@
-###################################################################
-# This file serves as a base configuration for testing purposes #
-# only. It is not intended for production use. #
-###################################################################
-
-ALLOWED_HOSTS = ['*']
-
-DATABASE = {
- 'NAME': 'netbox',
- 'USER': 'netbox',
- 'PASSWORD': 'netbox',
- 'HOST': 'localhost',
- 'PORT': '',
- 'CONN_MAX_AGE': 300,
-}
-
-PLUGINS = [
- 'netbox_secrets',
-]
-
-REDIS = {
- 'tasks': {
- 'HOST': 'localhost',
- 'PORT': 6379,
- 'PASSWORD': '',
- 'DATABASE': 0,
- 'SSL': False,
- },
- 'caching': {
- 'HOST': 'localhost',
- 'PORT': 6379,
- 'PASSWORD': '',
- 'DATABASE': 1,
- 'SSL': False,
- }
-}
-
-SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
diff --git a/.github/verify-bundles.sh b/.github/verify-bundles.sh
index 4a3b403..7922e6e 100644
--- a/.github/verify-bundles.sh
+++ b/.github/verify-bundles.sh
@@ -8,7 +8,7 @@
echo "$PWD"
-PROJECT_STATIC="$PWD/netbox-secrets/netbox_secrets/project-static"
+PROJECT_STATIC="$PWD/netbox_secrets/project-static"
DIST="$PROJECT_STATIC/dist/"
# Bundle static assets.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..05f2ec8
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,69 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+# This ensures that previous jobs for the workflow are canceled when the ref is
+# updated.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ lint:
+ runs-on: misc
+ name: Checks syntax of our code
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ # Full git history is needed to get a proper list of changed files within `super-linter`
+ fetch-depth: 0
+ - uses: actions/setup-python@v4
+ with:
+ python-version: 3.9
+ - name: Lint Code Base
+ uses: github/super-linter/slim@v4
+ env:
+ DEFAULT_BRANCH: dev
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SUPPRESS_POSSUM: true
+ LINTER_RULES_PATH: /
+ VALIDATE_ALL_CODEBASE: false
+ VALIDATE_DOCKERFILE: false
+ VALIDATE_JSCPD: true
+ FILTER_REGEX_EXCLUDE: (.*/)?(configuration/.*)
+ PYTHON_BLACK_CONFIG_FILE: pyproject.toml
+ PYTHON_FLAKE8_CONFIG_FILE: pyproject.toml
+ PYTHON_ISORT_CONFIG_FILE: pyproject.toml
+ test:
+ strategy:
+ matrix:
+ test_cmd:
+ - ./test.sh feature
+ - ./test.sh snapshot
+ - ./test.sh latest
+ - ./test.sh v3.3.10
+ fail-fast: false
+ runs-on: misc
+ name: Runs plugin tests
+ steps:
+ - id: git-checkout
+ name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: 16
+ - run: yarn --cwd netbox_secrets/project-static
+
+ - name: Check UI ESLint, TypeScript, and Prettier Compliance
+ run: yarn --cwd netbox_secrets/project-static validate
+
+ - name: Validate Static Asset Integrity
+ run: bash .github/verify-bundles.sh
+
+ - id: docker-test
+ name: Test the image
+ run: ${{ matrix.test_cmd }}
diff --git a/.gitignore b/.gitignore
index a51858b..acdbc5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,220 @@
-netbox_secrets.egg-info
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+**/__pycache__/
+/netbox_secrets.egg-info
node_modules
yarn.error
/netbox_secrets/project-static/.cache/
/netbox_secrets/project-static/netbox/
-__pycache__
build
dist
-.idea
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..0838f44
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..9216566
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..a1a41ea
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/netbox-secrets.iml b/.idea/netbox-secrets.iml
new file mode 100644
index 0000000..c410d0c
--- /dev/null
+++ b/.idea/netbox-secrets.iml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.jscpd.json b/.jscpd.json
new file mode 100644
index 0000000..263079f
--- /dev/null
+++ b/.jscpd.json
@@ -0,0 +1,3 @@
+{
+ "ignore": ["**/tests/**"]
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..85ff6d0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+ARG NETBOX_VARIANT=v3.3
+
+FROM registry.tangience.net/netbox/netbox:${NETBOX_VARIANT}
+
+USER root
+
+# Remove pre-installed plugin
+RUN rm -rf /usr/local/lib/python3.10/site-packages/netbox_secrets
+
+RUN mkdir -pv /plugins/netbox-secrets
+COPY . /plugins/netbox-secrets
+
+RUN python3 /plugins/netbox-secrets/setup.py develop
+RUN cp -rf /plugins/netbox-secrets/netbox_secrets/ /usr/local/lib/python3.10/site-packages/netbox_secrets
+
+USER $USER
diff --git a/configuration/configuration.py b/configuration/configuration.py
new file mode 100644
index 0000000..cdc99a7
--- /dev/null
+++ b/configuration/configuration.py
@@ -0,0 +1,86 @@
+####
+## We recommend to not edit this file.
+## Create separate files to overwrite the settings.
+## See `extra.py` as an example.
+####
+
+from os import environ
+from os.path import abspath, dirname
+
+# For reference see https://netbox.readthedocs.io/en/stable/configuration/
+# Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py
+
+# Read secret from file
+def _read_secret(secret_name, default=None):
+ try:
+ f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
+ except EnvironmentError:
+ return default
+ else:
+ with f:
+ return f.readline().strip()
+
+
+_BASE_DIR = dirname(dirname(abspath(__file__)))
+
+#########################
+# #
+# Required settings #
+# #
+#########################
+
+# This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write
+# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
+#
+# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
+ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ')
+
+# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
+# https://docs.djangoproject.com/en/stable/ref/settings/#databases
+DATABASE = {
+ 'NAME': environ.get('DB_NAME', 'netbox'), # Database name
+ 'USER': environ.get('DB_USER', ''), # PostgreSQL username
+ 'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')),
+ # PostgreSQL password
+ 'HOST': environ.get('DB_HOST', 'localhost'), # Database server
+ 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
+ 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
+ # Database connection SSLMODE
+ 'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
+ # Max database connection age
+ 'DISABLE_SERVER_SIDE_CURSORS': environ.get('DB_DISABLE_SERVER_SIDE_CURSORS', 'False').lower() == 'true',
+ # Disable the use of server-side cursors transaction pooling
+}
+
+# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
+# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
+# to use two separate database IDs.
+REDIS = {
+ 'tasks': {
+ 'HOST': environ.get('REDIS_HOST', 'localhost'),
+ 'PORT': int(environ.get('REDIS_PORT', 6379)),
+ 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
+ 'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
+ 'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
+ 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False').lower() == 'true',
+ },
+ 'caching': {
+ 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
+ 'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
+ 'PASSWORD': _read_secret(
+ 'redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))
+ ),
+ 'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
+ 'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
+ 'INSECURE_SKIP_TLS_VERIFY': environ.get(
+ 'REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False')
+ ).lower()
+ == 'true',
+ },
+}
+
+# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
+# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
+# symbols. NetBox will not run without this defined. For more information, see
+# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
+SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
diff --git a/configuration/logging.py b/configuration/logging.py
new file mode 100644
index 0000000..86914ae
--- /dev/null
+++ b/configuration/logging.py
@@ -0,0 +1,11 @@
+# Remove first comment(#) on each line to implement this working logging example.
+# Add LOGLEVEL environment variable to netbox if you use this example & want a different log level.
+from os import environ
+
+# Set LOGLEVEL in netbox.env or docker-compose.overide.yml to override a logging level of INFO.
+LOGLEVEL = environ.get("LOGLEVEL", "INFO")
+
+LOGGING = {
+ "version": 1,
+ "disable_existing_loggers": True,
+}
diff --git a/configuration/plugins.py b/configuration/plugins.py
new file mode 100644
index 0000000..58f9797
--- /dev/null
+++ b/configuration/plugins.py
@@ -0,0 +1,13 @@
+# Add your plugins and plugin settings here.
+# Of course uncomment this file out.
+
+# To learn how to build images with your required plugins
+# See https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins
+
+PLUGINS = [
+ "netbox_secrets",
+]
+
+PLUGINS_CONFIG = { # type: ignore
+ "netbox_secrets": {},
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..9b82488
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+version: '3.4'
+
+services:
+ netbox:
+ build:
+ dockerfile: Dockerfile
+ context: .
+ args:
+ NETBOX_VARIANT: ${NETBOX_VARIANT}
+ depends_on:
+ - postgres
+ - redis
+ env_file: env/netbox.env
+ volumes:
+ - ./configuration:/etc/netbox/config:z,ro
+
+ # postgres
+ postgres:
+ image: postgres:14-alpine
+ env_file: env/postgres.env
+
+ # redis
+ redis:
+ image: redis:6-alpine
+ command:
+ - sh
+ - -c # this is to evaluate the $REDIS_PASSWORD from the env
+ - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
+ env_file: env/redis.env
\ No newline at end of file
diff --git a/env/netbox.env b/env/netbox.env
new file mode 100644
index 0000000..e39dab5
--- /dev/null
+++ b/env/netbox.env
@@ -0,0 +1,23 @@
+ALLOWED_HOSTS=*
+CORS_ORIGIN_ALLOW_ALL=true
+DB_HOST=postgres
+DB_NAME=netbox
+DB_PASSWORD=J5brHrAXFLQSif0K
+DB_USER=netbox
+DEBUG=true
+ENFORCE_GLOBAL_UNIQUE=true
+LOGIN_REQUIRED=false
+GRAPHQL_ENABLED=true
+MAX_PAGE_SIZE=1000
+MEDIA_ROOT=/opt/netbox/netbox/media
+REDIS_DATABASE=0
+REDIS_HOST=redis
+REDIS_INSECURE_SKIP_TLS_VERIFY=false
+REDIS_PASSWORD=H733Kdjndks81
+SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
+SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
+SUPERUSER_EMAIL=admin@example.com
+SUPERUSER_NAME=admin
+SUPERUSER_PASSWORD=admin
+STARTUP_SCRIPTS=false
+WEBHOOKS_ENABLED=true
\ No newline at end of file
diff --git a/env/postgres.env b/env/postgres.env
new file mode 100644
index 0000000..045e64b
--- /dev/null
+++ b/env/postgres.env
@@ -0,0 +1,3 @@
+POSTGRES_DB=netbox
+POSTGRES_PASSWORD=J5brHrAXFLQSif0K
+POSTGRES_USER=netbox
\ No newline at end of file
diff --git a/env/redis.env b/env/redis.env
new file mode 100644
index 0000000..dcb03dd
--- /dev/null
+++ b/env/redis.env
@@ -0,0 +1 @@
+REDIS_PASSWORD=H733Kdjndks81
\ No newline at end of file
diff --git a/netbox_secrets/api/serializers.py b/netbox_secrets/api/serializers.py
index 36d54c3..5338d9d 100644
--- a/netbox_secrets/api/serializers.py
+++ b/netbox_secrets/api/serializers.py
@@ -14,7 +14,7 @@
# Secrets
#
-class SecretRoleSerializer(NestedGroupModelSerializer):
+class SecretRoleSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_secrets-api:secretrole-detail')
secret_count = serializers.IntegerField(read_only=True)
diff --git a/netbox_secrets/api/views.py b/netbox_secrets/api/views.py
index 216e11c..d9dd19b 100644
--- a/netbox_secrets/api/views.py
+++ b/netbox_secrets/api/views.py
@@ -11,8 +11,7 @@
from rest_framework.routers import APIRootView
from rest_framework.viewsets import ViewSet
-from extras.api.views import CustomFieldViewSet
-from netbox.api.viewsets import ModelViewSet
+from netbox.api.viewsets import NetBoxModelViewSet
from netbox_secrets import filtersets
from netbox_secrets.exceptions import InvalidKey
from netbox_secrets.models import Secret, SecretRole, SessionKey, UserKey
@@ -37,7 +36,7 @@ def get_view_name(self):
# Secret Roles
#
-class SecretRoleViewSet(CustomFieldViewSet):
+class SecretRoleViewSet(NetBoxModelViewSet):
queryset = SecretRole.objects.annotate(
secret_count=count_related(Secret, 'role')
)
@@ -49,7 +48,7 @@ class SecretRoleViewSet(CustomFieldViewSet):
# Secrets
#
-class SecretViewSet(ModelViewSet):
+class SecretViewSet(NetBoxModelViewSet):
queryset = Secret.objects.prefetch_related('role', 'tags')
serializer_class = serializers.SecretSerializer
filterset_class = filtersets.SecretFilterSet
diff --git a/netbox_secrets/forms/secrets.py b/netbox_secrets/forms/secrets.py
index 1ccd627..7f389dd 100644
--- a/netbox_secrets/forms/secrets.py
+++ b/netbox_secrets/forms/secrets.py
@@ -269,10 +269,6 @@ class UserKeyForm(forms.ModelForm):
class Meta:
model = UserKey
fields = ['public_key']
- help_texts = {
- 'public_key': "Enter your public RSA key. Keep the private one with you; you'll need it for decryption. "
- "Please note that passphrase-protected keys are not supported.",
- }
labels = {
'public_key': ''
}
diff --git a/netbox_secrets/migrations/0002_populate_userkeys.py b/netbox_secrets/migrations/0002_populate_userkeys.py
index 3f5cb48..7fe0dd7 100644
--- a/netbox_secrets/migrations/0002_populate_userkeys.py
+++ b/netbox_secrets/migrations/0002_populate_userkeys.py
@@ -3,7 +3,11 @@
def populate_userkeys(apps, schema_editor):
"""Populate the UserKey model with data from the SecretStore model."""
- UserKeyOld = apps.get_model('netbox_secretstore', 'UserKey')
+ try:
+ UserKeyOld = apps.get_model('netbox_secretstore', 'UserKey')
+ except LookupError:
+ # Skip if the old model doesn't exist
+ return
UserKey = apps.get_model('netbox_secrets', 'UserKey')
# Retrieve the necessary data from SecretStore objects
diff --git a/netbox_secrets/migrations/0003_populate_secretroles.py b/netbox_secrets/migrations/0003_populate_secretroles.py
index 04db7ab..e6f42ae 100644
--- a/netbox_secrets/migrations/0003_populate_secretroles.py
+++ b/netbox_secrets/migrations/0003_populate_secretroles.py
@@ -3,7 +3,11 @@
def populate_secretroles(apps, schema_editor):
"""Populate the SecretRole model with data from the SecretStore model."""
- SecretRoleOld = apps.get_model('netbox_secretstore', 'SecretRole')
+ try:
+ SecretRoleOld = apps.get_model('netbox_secretstore', 'SecretRole')
+ except LookupError:
+ # Skip if the old model doesn't exist
+ return
SecretRole = apps.get_model('netbox_secrets', 'SecretRole')
# Retrieve the necessary data from SecretStore objects
diff --git a/netbox_secrets/migrations/0004_populate_secrets.py b/netbox_secrets/migrations/0004_populate_secrets.py
index 203815c..79cb434 100644
--- a/netbox_secrets/migrations/0004_populate_secrets.py
+++ b/netbox_secrets/migrations/0004_populate_secrets.py
@@ -3,7 +3,11 @@
def populate_secrets(apps, schema_editor):
"""Populate the Secret model with data from the SecretStore model."""
- SecretOld = apps.get_model('netbox_secretstore', 'Secret')
+ try:
+ SecretOld = apps.get_model('netbox_secretstore', 'Secret')
+ except LookupError:
+ # Skip if the old model doesn't exist
+ return
Secret = apps.get_model('netbox_secrets', 'Secret')
# Retrieve the necessary data from SecretStore objects
diff --git a/netbox_secrets/models/secrets.py b/netbox_secrets/models/secrets.py
index 8ecd6a5..b60a600 100644
--- a/netbox_secrets/models/secrets.py
+++ b/netbox_secrets/models/secrets.py
@@ -4,6 +4,7 @@
from Crypto.PublicKey import RSA
from Crypto.Util import strxor
from django.conf import settings
+from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password, check_password
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
@@ -51,7 +52,8 @@ class UserKey(models.Model):
editable=False
)
public_key = models.TextField(
- verbose_name='RSA public key'
+ verbose_name='RSA public key',
+ help_text=_('Enter your public RSA key. Keep the private one with you; you will need it for decryption. Please note that passphrase-protected keys are not supported.')
)
master_key_cipher = models.BinaryField(
max_length=512,
@@ -332,6 +334,10 @@ def __str__(self):
def get_absolute_url(self):
return reverse('plugins:netbox_secrets:secret', args=[self.pk])
+ @classmethod
+ def get_prerequisite_models(cls):
+ return [SecretRole]
+
def to_csv(self):
return (
f'{self.assigned_object_type.app_label}.{self.assigned_object_type.model}',
diff --git a/netbox_secrets/navigation.py b/netbox_secrets/navigation.py
index 0b4efd1..16b8c9b 100644
--- a/netbox_secrets/navigation.py
+++ b/netbox_secrets/navigation.py
@@ -1,40 +1,50 @@
from extras.plugins import PluginMenuItem, PluginMenuButton
menu_items = (
- PluginMenuItem(link_text="User Key", link="plugins:netbox_secrets:userkey"),
+ PluginMenuItem(
+ link_text="User Key",
+ link="plugins:netbox_secrets:userkey",
+ permissions=["netbox_secrets.view_userkey"],
+ ),
PluginMenuItem(
link_text="Secret Roles",
link="plugins:netbox_secrets:secretrole_list",
+ permissions=["netbox_secrets.view_secretrole"],
buttons=(
PluginMenuButton(
link="plugins:netbox_secrets:secretrole_add",
title="Add Secret Role",
icon_class="mdi mdi-plus-thick",
color="green",
+ permissions=["netbox_secrets.add_secretrole"],
),
PluginMenuButton(
link="plugins:netbox_secrets:secretrole_import",
title="Import Secret Role",
icon_class="mdi mdi-upload",
color="teal",
+ permissions=["netbox_secrets.add_secretrole"],
),
),
),
PluginMenuItem(
link_text="Secrets",
link="plugins:netbox_secrets:secret_list",
+ permissions=["netbox_secrets.view_secret"],
buttons=(
PluginMenuButton(
link="plugins:netbox_secrets:secret_add",
title="Add Secret",
icon_class="mdi mdi-plus-thick",
color="green",
+ permissions=["netbox_secrets.add_secret"],
),
PluginMenuButton(
link="plugins:netbox_secrets:secret_import",
title="Import Secret",
icon_class="mdi mdi-upload",
color="teal",
+ permissions=["netbox_secrets.add_secret"],
),
),
),
diff --git a/netbox_secrets/tests/test_api.py b/netbox_secrets/tests/test_api.py
index e325947..5d0fbb1 100644
--- a/netbox_secrets/tests/test_api.py
+++ b/netbox_secrets/tests/test_api.py
@@ -75,8 +75,7 @@ class SecretTest(
):
model = Secret
view_namespace = 'plugins-api:netbox_secrets'
- brief_fields = ['assigned_object', 'assigned_object_id', 'assigned_object_type', 'created', 'custom_fields',
- 'display', 'hash', 'id', 'last_updated', 'name', 'plaintext', 'role', 'tags', 'url']
+ brief_fields = ['display', 'id', 'name', 'url']
def setUp(self):
super().setUp()
diff --git a/pre-commit b/pre-commit
new file mode 100755
index 0000000..25ddf23
--- /dev/null
+++ b/pre-commit
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Create a link to this file at .git/hooks/pre-commit
+
+exec 1>&2
+
+EXIT=0
+RED='\033[0;31m'
+NOCOLOR='\033[0m'
+
+echo "Running black..."
+black .
+if [ $? != 0 ]; then
+ EXIT=1
+fi
+
+echo "Running isort..."
+isort .
+if [ $? != 0 ]; then
+ EXIT=1
+fi
+
+echo "Running flake8..."
+flake8 .
+if [ $? != 0 ]; then
+ EXIT=1
+fi
+
+if [ $EXIT != 0 ]; then
+ printf "${RED}COMMIT FAILED${NOCOLOR}\n"
+fi
+
+exit $EXIT
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..74dd1fd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,22 @@
+# See PEP 518 for the spec of this file
+# https://www.python.org/dev/peps/pep-0518/
+
+[tool.black]
+line-length = 120
+target_version = ['py38', 'py39', 'py310']
+skip-string-normalization = true
+
+[tool.isort]
+profile = "black"
+
+[tool.pylint]
+max-line-length = 120
+
+[tool.pyright]
+include = ["netbox_secrets"]
+exclude = [
+ "**/node_modules",
+ "**/__pycache__",
+]
+reportMissingImports = true
+reportMissingTypeStubs = false
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..6bbaf9d
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Runs the NetBox plugin unit tests
+# Usage:
+# ./test.sh latest
+# ./test.sh v2.9.7
+# ./test.sh develop-2.10
+
+# exit when a command exits with an exit code != 0
+set -e
+
+# NETBOX_VARIANT is used by `Dockerfile` to determine the tag
+NETBOX_VARIANT="${1-latest}"
+
+# The docker compose command to use
+doco="docker compose --file docker-compose.yml"
+
+test_netbox_unit_tests() {
+ echo "⏱ Running NetBox Unit Tests"
+ $doco run --rm netbox python manage.py test netbox_secrets
+}
+
+test_cleanup() {
+ echo "💣 Cleaning Up"
+ $doco down -v
+ $doco rm -fsv
+ docker image rm docker.io/library/netbox-secrets-netbox || echo ''
+}
+
+export NETBOX_VARIANT=${NETBOX_VARIANT}
+
+echo "🐳🐳🐳 Start testing '${NETBOX_VARIANT}'"
+
+# Make sure the cleanup script is executed
+trap test_cleanup EXIT ERR
+test_netbox_unit_tests
+
+echo "🐳🐳🐳 Done testing '${NETBOX_VARIANT}'"