diff --git a/.flake8 b/.flake8 index 83a86a2..6872797 100644 --- a/.flake8 +++ b/.flake8 @@ -4,4 +4,4 @@ extend-ignore = E203, W503 per-file-ignores = configuration/*:E131,E251,E266,E302,E305,E501,E722 startup_scripts/startup_script_utils/__init__.py:F401 - docker/*:E266,E722 + docker/*:E266,E722,E501 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f4b6305..4eb4efd 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -23,7 +23,7 @@ jobs: packages: read statuses: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Full git history is needed to get a proper # list of changed files within `super-linter` @@ -42,6 +42,7 @@ jobs: VALIDATE_GITHUB_ACTIONS_ZIZMOR: false VALIDATE_GITLEAKS: false VALIDATE_JSCPD: false + VALIDATE_PYTHON_PYLINT: false VALIDATE_TRIVY: false FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*) EDITORCONFIG_FILE_NAME: .editorconfig-checker.json @@ -73,7 +74,7 @@ jobs: steps: - id: git-checkout name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - id: buildx-setup name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d83729..9314b8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: steps: - id: source-checkout name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ matrix.build.branch }} - id: set-netbox-docker-version diff --git a/Dockerfile b/Dockerfile index b398e9c..91f814f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ ARG NETBOX_PATH COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / ENV VIRTUAL_ENV=/opt/netbox/venv RUN \ - # Gunicorn is not needed because we use Nginx Unit + # Gunicorn is not needed because we use Granian sed -i -e '/gunicorn/d' /requirements.txt && \ # We need 'social-auth-core[all]' in the Docker image. But if we put it in our own requirements-container.txt # we have potential version conflicts and the build will fail. @@ -46,8 +46,6 @@ RUN \ ARG FROM FROM ${FROM} AS main -COPY docker/unit.list /etc/apt/sources.list.d/unit.list -ADD --chmod=444 --chown=0:0 https://unit.nginx.org/keys/nginx-keyring.gpg /usr/share/keyrings/nginx-keyring.gpg RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ && apt-get upgrade \ @@ -64,8 +62,6 @@ RUN export DEBIAN_FRONTEND=noninteractive \ openssl \ python3 \ tini \ - unit-python3.12=1.34.2-1~noble \ - unit=1.34.2-1~noble \ && rm -rf /var/lib/apt/lists/* # Copy the modified 'requirements*.txt' files, to have the files actually used during installation @@ -80,22 +76,23 @@ COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh +COPY docker/super_user.py /opt/netbox/super_user.py COPY configuration/ /etc/netbox/config/ -COPY docker/nginx-unit.json /etc/unit/ +COPY docker/granian.py /opt/netbox/netbox/netbox/granian.py COPY VERSION /opt/netbox/VERSION WORKDIR /opt/netbox/netbox # Must set permissions for '/opt/netbox/netbox/media' directory # to g+w so that pictures can be uploaded to netbox. -RUN mkdir -p static media /opt/unit/state/ /opt/unit/tmp/ \ - && chown -R unit:root /opt/unit/ media reports scripts \ - && chmod -R g+w /opt/unit/ media reports scripts \ - && cd /opt/netbox/ && SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build \ - --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ - && DEBUG="true" SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input \ - && mkdir /opt/netbox/netbox/local \ - && echo "build: Docker-$(cat /opt/netbox/VERSION)" > /opt/netbox/netbox/local/release.yaml +RUN useradd --home-dir /opt/netbox/ --no-create-home --no-user-group --system --shell /bin/false --uid 999 --gid 0 netbox \ + && mkdir -p static media local \ + && chown -R netbox:root media reports scripts \ + && chmod -R g+w media reports scripts \ + && cd /opt/netbox/ && SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build \ + --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ + && DEBUG="true" SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input \ + && echo "build: Docker-$(cat /opt/netbox/VERSION)" > /opt/netbox/netbox/local/release.yaml ENV LANG=C.utf8 PATH=/opt/netbox/venv/bin:$PATH VIRTUAL_ENV=/opt/netbox/venv UV_NO_CACHE=1 ENTRYPOINT [ "/usr/bin/tini", "--" ] diff --git a/VERSION b/VERSION index a423d42..0c89fc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.2 \ No newline at end of file +4.0.0 \ No newline at end of file diff --git a/configuration/configuration.py b/configuration/configuration.py index 8dfa736..f71786a 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -310,6 +310,12 @@ REMOTE_AUTH_SUPERUSER_GROUPS = _environ_get_and_map('REMOTE_AUTH_SUPERUSER_GROUP REMOTE_AUTH_SUPERUSERS = _environ_get_and_map('REMOTE_AUTH_SUPERUSERS', '', _AS_LIST) REMOTE_AUTH_STAFF_GROUPS = _environ_get_and_map('REMOTE_AUTH_STAFF_GROUPS', '', _AS_LIST) REMOTE_AUTH_STAFF_USERS = _environ_get_and_map('REMOTE_AUTH_STAFF_USERS', '', _AS_LIST) +# SSO Configuration +SOCIAL_AUTH_OKTA_OPENIDCONNECT_KEY = environ.get('SOCIAL_AUTH_OKTA_OPENIDCONNECT_KEY') +SOCIAL_AUTH_OKTA_OPENIDCONNECT_SECRET = _read_secret('okta_openidconnect_secret', environ.get('SOCIAL_AUTH_OKTA_OPENIDCONNECT_SECRET', '')) +SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL = environ.get('SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL') +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY') +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = _read_secret('google_oauth2_secret', environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', '')) # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the # version check or use the URL below to check for release in the official NetBox repository. diff --git a/configuration/extra.py b/configuration/extra.py index 8bd1337..3a10ea2 100644 --- a/configuration/extra.py +++ b/configuration/extra.py @@ -33,13 +33,20 @@ ## By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the -## class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example: -# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage' -# STORAGE_CONFIG = { -# 'AWS_ACCESS_KEY_ID': 'Key ID', -# 'AWS_SECRET_ACCESS_KEY': 'Secret', -# 'AWS_STORAGE_BUCKET_NAME': 'netbox', -# 'AWS_S3_REGION_NAME': 'eu-west-1', +## class path of the storage driver and any configuration options in STORAGES. For example: +# STORAGES = { +# 'default': { +# 'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage', +# 'OPTIONS': { +# 'access_key': 'Key ID', +# 'secret_key': 'Secret', +# 'bucket_name': 'netbox', +# 'region_name': 'us-west-1', +# } +# }, +# 'staticfiles': { +# 'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage', +# } # } diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example index d7ef961..8c2ff70 100644 --- a/docker-compose.override.yml.example +++ b/docker-compose.override.yml.example @@ -2,9 +2,6 @@ services: netbox: ports: - "8000:8080" - # If you want the Nginx unit status page visible from the - # outside of the container add the following port mapping: - # - "8001:8081" # healthcheck: # Time for which the health check can fail after the container is started. # This depends mostly on the performance of your database. On the first start, @@ -19,4 +16,18 @@ services: # SUPERUSER_EMAIL: "" # SUPERUSER_NAME: "" # SUPERUSER_PASSWORD: "" + # SSO Configuration + # SOCIAL_AUTH_OKTA_OPENIDCONNECT_KEY: "your_okta_client_id" + # SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL: "https://your-domain.okta.com" + # SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: "your_google_client_id" + # secrets: + # - okta_openidconnect_secret + # - google_oauth2_secret + +# Uncomment to use Docker secrets for SSO credentials +# secrets: +# okta_openidconnect_secret: +# file: ./secrets/okta_secret.txt +# google_oauth2_secret: +# file: ./secrets/google_secret.txt diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 9388022..8e22aa6 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -9,7 +9,7 @@ services: redis-cache: condition: service_healthy env_file: env/netbox.env - user: "unit:root" + user: "netbox:root" volumes: - ./test-configuration/test_config.py:/etc/netbox/config/test_config.py:z,ro healthcheck: @@ -30,7 +30,7 @@ services: interval: 15s postgres: - image: docker.io/postgres:17-alpine + image: docker.io/postgres:18-alpine env_file: env/postgres.env healthcheck: test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER ## $$ because of docker-compose @@ -40,7 +40,7 @@ services: retries: 5 redis: &redis - image: docker.io/valkey/valkey:8.1-alpine + image: docker.io/valkey/valkey:9.0-alpine command: - sh - -c # this is to evaluate the $REDIS_PASSWORD from the env diff --git a/docker-compose.yml b/docker-compose.yml index 0af361c..64c67ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,12 @@ services: netbox: &netbox - image: docker.io/netboxcommunity/netbox:${VERSION-v4.4-3.4.1} + image: docker.io/netboxcommunity/netbox:${VERSION-v4.5-4.0.0} depends_on: - postgres - redis - redis-cache env_file: env/netbox.env - user: "unit:root" + user: "netbox:root" healthcheck: test: curl -f http://localhost:8080/login/ || exit 1 start_period: 90s @@ -34,7 +34,7 @@ services: # postgres postgres: - image: docker.io/postgres:17-alpine + image: docker.io/postgres:18-alpine healthcheck: test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER start_period: 20s @@ -43,11 +43,11 @@ services: retries: 5 env_file: env/postgres.env volumes: - - netbox-postgres-data:/var/lib/postgresql/data + - netbox-postgres:/var/lib/postgresql # redis redis: - image: docker.io/valkey/valkey:8.1-alpine + image: docker.io/valkey/valkey:9.0-alpine command: - sh - -c # this is to evaluate the $REDIS_PASSWORD from the env @@ -62,7 +62,7 @@ services: volumes: - netbox-redis-data:/data redis-cache: - image: docker.io/valkey/valkey:8.1-alpine + image: docker.io/valkey/valkey:9.0-alpine command: - sh - -c # this is to evaluate the $REDIS_PASSWORD from the env @@ -75,7 +75,7 @@ services: volumes: netbox-media-files: driver: local - netbox-postgres-data: + netbox-postgres: driver: local netbox-redis-cache-data: driver: local diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index fa5930d..1d36167 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -54,43 +54,10 @@ fi if [ "$SKIP_SUPERUSER" == "true" ]; then echo "â†Šī¸ Skip creating the superuser" else - if [ -z ${SUPERUSER_NAME+x} ]; then - SUPERUSER_NAME='admin' - fi - if [ -z ${SUPERUSER_EMAIL+x} ]; then - SUPERUSER_EMAIL='admin@example.com' - fi - if [ -f "/run/secrets/superuser_password" ]; then - SUPERUSER_PASSWORD="$( str | None: + try: + f = open("/run/secrets/" + secret_name, "r", encoding="utf-8") + except EnvironmentError: + return default + else: + with f: + return f.readline().strip() + + +su_name = environ.get("SUPERUSER_NAME", "admin") +su_email = environ.get("SUPERUSER_EMAIL", "admin@example.com") +su_password = _read_secret("superuser_password", environ.get("SUPERUSER_PASSWORD", "admin")) +su_api_token = _read_secret( + "superuser_api_token", + environ.get("SUPERUSER_API_TOKEN", "0123456789abcdef0123456789abcdef01234567"), +) + +if not User.objects.filter(username=su_name): + u = User.objects.create_superuser(su_name, su_email, su_password) + msg = "" + if not settings.API_TOKEN_PEPPERS: + print("âš ī¸ No API token will be created as API_TOKEN_PEPPERS is not set") + msg = f"💡 Superuser Username: {su_name}, E-Mail: {su_email}" + else: + t = Token.objects.create(user=u, token=su_api_token, version=TokenVersionChoices.V2) + msg = f"💡 Superuser Username: {su_name}, E-Mail: {su_email}, API Token: {t} (use with '{t.get_auth_header_prefix()}')" + print(msg) diff --git a/docker/unit.list b/docker/unit.list deleted file mode 100644 index 6193723..0000000 --- a/docker/unit.list +++ /dev/null @@ -1 +0,0 @@ -deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] http://packages.nginx.org/unit/ubuntu/ noble unit diff --git a/env/netbox.env b/env/netbox.env index 52fca3b..c7b97db 100644 --- a/env/netbox.env +++ b/env/netbox.env @@ -15,6 +15,8 @@ EMAIL_USERNAME=netbox # EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`! EMAIL_USE_SSL=false EMAIL_USE_TLS=false +GRANIAN_BACKPRESSURE=4 +GRANIAN_WORKERS=4 GRAPHQL_ENABLED=true MEDIA_ROOT=/opt/netbox/netbox/media METRICS_ENABLED=false @@ -31,4 +33,12 @@ REDIS_SSL=false RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X' SKIP_SUPERUSER=true +# SSO Configuration (uncomment and configure as needed) +# OKTA OpenID Connect +# SOCIAL_AUTH_OKTA_OPENIDCONNECT_KEY=your_okta_client_id +# SOCIAL_AUTH_OKTA_OPENIDCONNECT_SECRET=your_okta_client_secret +# SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL=https://your-domain.okta.com +# Google OAuth2 +# SOCIAL_AUTH_GOOGLE_OAUTH2_KEY=your_google_client_id +# SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET=your_google_client_secret WEBHOOKS_ENABLED=true diff --git a/requirements-container.txt b/requirements-container.txt index a42196f..ebe01cb 100644 --- a/requirements-container.txt +++ b/requirements-container.txt @@ -1,6 +1,7 @@ -django-auth-ldap==5.2.0 -dulwich==0.24.8 +django-auth-ldap==5.3.0 +dulwich==1.0.0 +granian[uvloop]==2.7.0 python3-saml==1.16.0 --no-binary lxml --no-binary xmlsec -sentry-sdk[django]==2.43.0 +sentry-sdk[django]==2.51.0