diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 9035b9a8..d7dfb864 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -41,3 +41,7 @@ jobs: target: web tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + # Attach SLSA build provenance + SBOM so a swapped/backdoored image can be + # detected against the attestation at deploy time (chain C-005). + provenance: true + sbom: true diff --git a/.gitignore b/.gitignore index 69e3b55b..e96a0d10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ config.json + +# Real secrets must never be committed (chain C-004) — only *.sample templates. +secrets/*.env + __pycache__/ *.py[cod] db.sqlite3 diff --git a/Access/views.py b/Access/views.py index 7588e2f4..007990bf 100644 --- a/Access/views.py +++ b/Access/views.py @@ -337,9 +337,18 @@ def pending_requests(request): @login_required +@user_any_approver def accept_bulk(request, selector): """approve one or more access request. + F-022: gate the bulk-approval endpoint with @user_any_approver (matching the + sibling views pending_requests / decline_access) so a logged-in NON-approver + cannot reach the approval paths at all. This checks "holds ANY approver + permission" via getPossibleApproverPermissions(), so module-specific approvers + are unaffected. Per-request authorization (module/group approver perms, primary/ + secondary checks, self-approval block) is still enforced in the helpers below — + this is a defense-in-depth view gate, not a replacement for those checks. + Args: request (HTTPRequest): Details of access and access approver. selector (str): access type being approved. diff --git a/Dockerfile b/Dockerfile index 33154505..9b770227 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,10 +16,15 @@ RUN DEBIAN_FRONTEND=noninteractive \ # Set env variables used in this Dockerfile (add a unique prefix, such as DEV) RUN apt update && apt install -y netcat dnsutils libmariadb-dev -RUN mkdir -p /ebs/logs && touch /ebs/logs/engima.log && chmod 777 /ebs/logs/engima.log - ARG APPUID=1001 -RUN useradd -rm -d /home/app -s /bin/bash -g root -u "$APPUID" app +# Own group for the app user (not gid 0 / root group) so a container escape +# does not inherit root-group file access (chains C-001 / C-003). +RUN groupadd -g "$APPUID" app \ + && useradd -rm -d /home/app -s /bin/bash -g app -u "$APPUID" app + +# Log file owned by the app user, not world-writable (was chmod 777). +RUN mkdir -p /ebs/logs && touch /ebs/logs/engima.log \ + && chown -R app:app /ebs/logs && chmod 640 /ebs/logs/engima.log WORKDIR /srv/code/dev RUN mkdir -p logs RUN mkdir -p db diff --git a/EnigmaAutomation/settings.py b/EnigmaAutomation/settings.py index e89aa40b..5a0d5ef3 100644 --- a/EnigmaAutomation/settings.py +++ b/EnigmaAutomation/settings.py @@ -49,6 +49,30 @@ CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True +# --- Response security headers (chain C-002 / F-035) --- +# Sent by django.middleware.security.SecurityMiddleware + clickjacking middleware. +SECURE_CONTENT_TYPE_NOSNIFF = True +SECURE_REFERRER_POLICY = "same-origin" +X_FRAME_OPTIONS = "DENY" + +# --- Content-Security-Policy (django-csp / csp.middleware.CSPMiddleware) --- +# NOTE: 'unsafe-inline' is retained for script/style because the ops UI still +# ships inline