diff --git a/Dockerfile b/Dockerfile index 32303b1e88..ceedb0f8d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api RUN npm run build # Stage 3: Build go proxy -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22.3-bookworm AS go-builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder ARG TARGETOS ARG TARGETARCH @@ -49,6 +49,11 @@ ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=cache,target=/go/pkg/mod \ @@ -63,11 +68,11 @@ COPY ./internal /go/src/goauthentik.io/internal COPY ./go.mod /go/src/goauthentik.io/go.mod COPY ./go.sum /go/src/goauthentik.io/go.sum -ENV CGO_ENABLED=0 - RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ - GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ + go build -o /go/authentik ./cmd/server # Stage 4: MaxMind GeoIP FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 as geoip @@ -84,7 +89,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" # Stage 5: Python dependencies -FROM docker.io/python:3.12.3-slim-bookworm AS python-deps +FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS python-deps WORKDIR /ak-root/poetry @@ -97,7 +102,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ apt-get update && \ # Required for installing pip packages - apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev + apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \ --mount=type=bind,target=./poetry.lock,src=./poetry.lock \ @@ -105,12 +110,13 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \ --mount=type=cache,target=/root/.cache/pypoetry \ python -m venv /ak-root/venv/ && \ bash -c "source ${VENV_PATH}/bin/activate && \ - pip3 install --upgrade pip && \ - pip3 install poetry && \ - poetry install --only=main --no-ansi --no-interaction --no-root" + pip3 install --upgrade pip && \ + pip3 install poetry && \ + poetry install --only=main --no-ansi --no-interaction --no-root && \ + pip install --force-reinstall /wheels/*" # Stage 6: Run -FROM docker.io/python:3.12.3-slim-bookworm AS final-image +FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS final-image ARG GIT_BUILD_HASH ARG VERSION @@ -127,7 +133,7 @@ WORKDIR / # We cannot cache this layer otherwise we'll end up with a bigger image RUN apt-get update && \ # Required for runtime - apt-get install -y --no-install-recommends libpq5 openssl libxmlsec1-openssl libmaxminddb0 ca-certificates && \ + apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \ # Required for bootstrap & healtcheck apt-get install -y --no-install-recommends runit && \ apt-get clean && \ @@ -163,6 +169,8 @@ ENV TMPDIR=/dev/shm/ \ VENV_PATH="/ak-root/venv" \ POETRY_VIRTUALENVS_CREATE=false +ENV GOFIPS=1 + HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ] ENTRYPOINT [ "dumb-init", "--", "ak" ] diff --git a/authentik/admin/api/system.py b/authentik/admin/api/system.py index 442771c218..b0e7f074fd 100644 --- a/authentik/admin/api/system.py +++ b/authentik/admin/api/system.py @@ -2,17 +2,19 @@ import platform from datetime import datetime +from ssl import OPENSSL_VERSION from sys import version as python_version from typing import TypedDict +from cryptography.hazmat.backends.openssl.backend import backend from django.utils.timezone import now from drf_spectacular.utils import extend_schema -from gunicorn import version_info as gunicorn_version from rest_framework.fields import SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from authentik import get_full_version from authentik.core.api.utils import PassiveSerializer from authentik.lib.config import CONFIG from authentik.lib.utils.reflection import get_env @@ -25,11 +27,13 @@ class RuntimeDict(TypedDict): """Runtime information""" python_version: str - gunicorn_version: str environment: str architecture: str platform: str uname: str + openssl_version: str + openssl_fips_mode: bool + authentik_version: str class SystemInfoSerializer(PassiveSerializer): @@ -64,11 +68,13 @@ class SystemInfoSerializer(PassiveSerializer): def get_runtime(self, request: Request) -> RuntimeDict: """Get versions""" return { - "python_version": python_version, - "gunicorn_version": ".".join(str(x) for x in gunicorn_version), - "environment": get_env(), "architecture": platform.machine(), + "authentik_version": get_full_version(), + "environment": get_env(), + "openssl_fips_enabled": backend._fips_enabled, + "openssl_version": OPENSSL_VERSION, "platform": platform.platform(), + "python_version": python_version, "uname": " ".join(platform.uname()), } diff --git a/authentik/core/tests/test_users_avatars.py b/authentik/core/tests/test_users_avatars.py index 2dcfaed921..b27fff8495 100644 --- a/authentik/core/tests/test_users_avatars.py +++ b/authentik/core/tests/test_users_avatars.py @@ -42,8 +42,8 @@ class TestUsersAvatars(APITestCase): with Mocker() as mocker: mocker.head( ( - "https://secure.gravatar.com/avatar/84730f9c1851d1ea03f1a" - "a9ed85bd1ea?size=158&rating=g&default=404" + "https://www.gravatar.com/avatar/76eb3c74c8beb6faa037f1b6e2ecb3e252bdac" + "6cf71fb567ae36025a9d4ea86b?size=158&rating=g&default=404" ), text="foo", ) diff --git a/authentik/crypto/models.py b/authentik/crypto/models.py index 444b31ce62..977c1e83b4 100644 --- a/authentik/crypto/models.py +++ b/authentik/crypto/models.py @@ -92,7 +92,11 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel): @property def kid(self): """Get Key ID used for JWKS""" - return md5(self.key_data.encode("utf-8")).hexdigest() if self.key_data else "" # nosec + return ( + md5(self.key_data.encode("utf-8"), usedforsecurity=False).hexdigest() + if self.key_data + else "" + ) # nosec def __str__(self) -> str: return f"Certificate-Key Pair {self.name}" diff --git a/authentik/lib/avatars.py b/authentik/lib/avatars.py index 0a7f44b340..7f5fe0c72c 100644 --- a/authentik/lib/avatars.py +++ b/authentik/lib/avatars.py @@ -2,7 +2,7 @@ from base64 import b64encode from functools import cache as funccache -from hashlib import md5 +from hashlib import md5, sha256 from typing import TYPE_CHECKING from urllib.parse import urlencode @@ -20,7 +20,7 @@ from authentik.tenants.utils import get_current_tenant if TYPE_CHECKING: from authentik.core.models import User -GRAVATAR_URL = "https://secure.gravatar.com" +GRAVATAR_URL = "https://www.gravatar.com" DEFAULT_AVATAR = static("dist/assets/images/user_default.png") CACHE_KEY_GRAVATAR = "goauthentik.io/lib/avatars/" CACHE_KEY_GRAVATAR_AVAILABLE = "goauthentik.io/lib/avatars/gravatar_available" @@ -55,10 +55,9 @@ def avatar_mode_gravatar(user: "User", mode: str) -> str | None: if not cache.get(CACHE_KEY_GRAVATAR_AVAILABLE, True): return None - # gravatar uses md5 for their URLs, so md5 can't be avoided - mail_hash = md5(user.email.lower().encode("utf-8")).hexdigest() # nosec - parameters = [("size", "158"), ("rating", "g"), ("default", "404")] - gravatar_url = f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}" + mail_hash = sha256(user.email.lower().encode("utf-8")).hexdigest() # nosec + parameters = {"size": "158", "rating": "g", "default": "404"} + gravatar_url = f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters)}" full_key = CACHE_KEY_GRAVATAR + mail_hash if cache.has_key(full_key): @@ -84,7 +83,9 @@ def avatar_mode_gravatar(user: "User", mode: str) -> str | None: def generate_colors(text: str) -> tuple[str, str]: """Generate colours based on `text`""" - color = int(md5(text.lower().encode("utf-8")).hexdigest(), 16) % 0xFFFFFF # nosec + color = ( + int(md5(text.lower().encode("utf-8"), usedforsecurity=False).hexdigest(), 16) % 0xFFFFFF + ) # nosec # Get a (somewhat arbitrarily) reduced scope of colors # to avoid too dark or light backgrounds @@ -179,7 +180,7 @@ def avatar_mode_generated(user: "User", mode: str) -> str | None: def avatar_mode_url(user: "User", mode: str) -> str | None: """Format url""" - mail_hash = md5(user.email.lower().encode("utf-8")).hexdigest() # nosec + mail_hash = md5(user.email.lower().encode("utf-8"), usedforsecurity=False).hexdigest() # nosec return mode % { "username": user.username, "mail_hash": mail_hash, diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index cf905ec105..f5bde93c77 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -117,8 +117,12 @@ class OutpostHealthSerializer(PassiveSerializer): uid = CharField(read_only=True) last_seen = DateTimeField(read_only=True) version = CharField(read_only=True) - version_should = CharField(read_only=True) + golang_version = CharField(read_only=True) + openssl_enabled = BooleanField(read_only=True) + openssl_version = CharField(read_only=True) + fips_enabled = BooleanField(read_only=True) + version_should = CharField(read_only=True) version_outdated = BooleanField(read_only=True) build_hash = CharField(read_only=True, required=False) @@ -173,6 +177,10 @@ class OutpostViewSet(UsedByMixin, ModelViewSet): "version_should": state.version_should, "version_outdated": state.version_outdated, "build_hash": state.build_hash, + "golang_version": state.golang_version, + "openssl_enabled": state.openssl_enabled, + "openssl_version": state.openssl_version, + "fips_enabled": state.fips_enabled, "hostname": state.hostname, "build_hash_should": get_build_hash(), } diff --git a/authentik/outposts/consumer.py b/authentik/outposts/consumer.py index f635de60db..80b64999d5 100644 --- a/authentik/outposts/consumer.py +++ b/authentik/outposts/consumer.py @@ -121,6 +121,10 @@ class OutpostConsumer(JsonWebsocketConsumer): if msg.instruction == WebsocketMessageInstruction.HELLO: state.version = msg.args.pop("version", None) state.build_hash = msg.args.pop("buildHash", "") + state.golang_version = msg.args.pop("golangVersion", "") + state.openssl_enabled = msg.args.pop("opensslEnabled", False) + state.openssl_version = msg.args.pop("opensslVersion", "") + state.fips_enabled = msg.args.pop("fipsEnabled", False) state.args.update(msg.args) elif msg.instruction == WebsocketMessageInstruction.ACK: return diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index 29984ebd62..1a10e11c3d 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -131,7 +131,7 @@ class OutpostServiceConnection(models.Model): verbose_name = _("Outpost Service-Connection") verbose_name_plural = _("Outpost Service-Connections") - def __str__(self) -> __version__: + def __str__(self) -> str: return f"Outpost service connection {self.name}" @property @@ -434,6 +434,10 @@ class OutpostState: version: str | None = field(default=None) version_should: Version = field(default=OUR_VERSION) build_hash: str = field(default="") + golang_version: str = field(default="") + openssl_enabled: bool = field(default=False) + openssl_version: str = field(default="") + fips_enabled: bool = field(default=False) hostname: str = field(default="") args: dict = field(default_factory=dict) diff --git a/authentik/root/test_plugin.py b/authentik/root/test_plugin.py index 2360a5021d..815b8e485b 100644 --- a/authentik/root/test_plugin.py +++ b/authentik/root/test_plugin.py @@ -1,6 +1,8 @@ from os import environ +from ssl import OPENSSL_VERSION import pytest +from cryptography.hazmat.backends.openssl.backend import backend from authentik import get_full_version @@ -18,4 +20,7 @@ def pytest_sessionstart(*_, **__): @pytest.hookimpl(trylast=True) def pytest_report_header(*_, **__): """Add authentik version to pytest output""" - return [f"authentik version: {get_full_version()}"] + return [ + f"authentik version: {get_full_version()}", + f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}", + ] diff --git a/internal/crypto/backend/fips_disabled.go b/internal/crypto/backend/fips_disabled.go new file mode 100644 index 0000000000..418cd1a144 --- /dev/null +++ b/internal/crypto/backend/fips_disabled.go @@ -0,0 +1,5 @@ +//go:build requirefips + +package backend + +var FipsEnabled = true diff --git a/internal/crypto/backend/fips_enabled.go b/internal/crypto/backend/fips_enabled.go new file mode 100644 index 0000000000..49a0c95bb4 --- /dev/null +++ b/internal/crypto/backend/fips_enabled.go @@ -0,0 +1,5 @@ +//go:build !requirefips + +package backend + +var FipsEnabled = false diff --git a/internal/crypto/backend/openssl_disabled.go b/internal/crypto/backend/openssl_disabled.go new file mode 100644 index 0000000000..aeb9e85404 --- /dev/null +++ b/internal/crypto/backend/openssl_disabled.go @@ -0,0 +1,5 @@ +//go:build !goexperiment.systemcrypto + +package backend + +var OpensslEnabled = false diff --git a/internal/crypto/backend/openssl_enabled.go b/internal/crypto/backend/openssl_enabled.go new file mode 100644 index 0000000000..11c4d1b081 --- /dev/null +++ b/internal/crypto/backend/openssl_enabled.go @@ -0,0 +1,5 @@ +//go:build goexperiment.systemcrypto + +package backend + +var OpensslEnabled = true diff --git a/internal/crypto/backend/openssl_version.go b/internal/crypto/backend/openssl_version.go new file mode 100644 index 0000000000..80d5bbd119 --- /dev/null +++ b/internal/crypto/backend/openssl_version.go @@ -0,0 +1,17 @@ +package backend + +import ( + "bytes" + "os/exec" +) + +func OpensslVersion() string { + cmd := exec.Command("openssl", "version") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "" + } + return out.String() +} diff --git a/internal/outpost/ak/api.go b/internal/outpost/ak/api.go index 1f744010a7..fca9a6fb9c 100644 --- a/internal/outpost/ak/api.go +++ b/internal/outpost/ak/api.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "os/signal" + "runtime" "syscall" "time" @@ -15,11 +16,12 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "goauthentik.io/api/v3" "goauthentik.io/internal/constants" + cryptobackend "goauthentik.io/internal/crypto/backend" "goauthentik.io/internal/utils/web" - - log "github.com/sirupsen/logrus" ) type WSHandler func(ctx context.Context, args map[string]interface{}) @@ -184,9 +186,13 @@ func (a *APIController) OnRefresh() error { func (a *APIController) getWebsocketPingArgs() map[string]interface{} { args := map[string]interface{}{ - "version": constants.VERSION, - "buildHash": constants.BUILD("tagged"), - "uuid": a.instanceUUID.String(), + "version": constants.VERSION, + "buildHash": constants.BUILD("tagged"), + "uuid": a.instanceUUID.String(), + "golangVersion": runtime.Version(), + "opensslEnabled": cryptobackend.OpensslEnabled, + "opensslVersion": cryptobackend.OpensslVersion(), + "fipsEnabled": cryptobackend.FipsEnabled, } hostname, err := os.Hostname() if err == nil { diff --git a/ldap.Dockerfile b/ldap.Dockerfile index 4f728d6e47..44347e6738 100644 --- a/ldap.Dockerfile +++ b/ldap.Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # Stage 1: Build -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22.3-bookworm AS builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS builder ARG TARGETOS ARG TARGETARCH @@ -12,20 +12,26 @@ ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \ --mount=type=cache,target=/go/pkg/mod \ go mod download -ENV CGO_ENABLED=0 COPY . . RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ - GOARM="${TARGETVARIANT#v}" go build -o /go/ldap ./cmd/ldap + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ + go build -o /go/ldap ./cmd/ldap # Stage 2: Run -FROM gcr.io/distroless/static-debian11:debug +FROM ghcr.io/goauthentik/fips-debian:bookworm-slim-fips ARG GIT_BUILD_HASH ENV GIT_BUILD_HASH=$GIT_BUILD_HASH @@ -44,4 +50,6 @@ EXPOSE 3389 6636 9300 USER 1000 +ENV GOFIPS=1 + ENTRYPOINT ["/ldap"] diff --git a/lifecycle/ak b/lifecycle/ak index 615bfe9beb..2ae5f4792f 100755 --- a/lifecycle/ak +++ b/lifecycle/ak @@ -84,6 +84,7 @@ elif [[ "$1" == "bash" ]]; then elif [[ "$1" == "test-all" ]]; then prepare_debug chmod 777 /root + pip install --force-reinstall /wheels/* check_if_root "python -m manage test authentik" elif [[ "$1" == "healthcheck" ]]; then run_authentik healthcheck $(cat $MODE_FILE) diff --git a/lifecycle/gunicorn.conf.py b/lifecycle/gunicorn.conf.py index 600f265b7e..c158680697 100644 --- a/lifecycle/gunicorn.conf.py +++ b/lifecycle/gunicorn.conf.py @@ -7,6 +7,9 @@ from pathlib import Path from tempfile import gettempdir from typing import TYPE_CHECKING +from cryptography.exceptions import InternalError +from cryptography.hazmat.backends.openssl.backend import backend +from defusedxml import defuse_stdlib from prometheus_client.values import MultiProcessValue from authentik import get_full_version @@ -25,6 +28,13 @@ if TYPE_CHECKING: from authentik.root.asgi import AuthentikAsgi +defuse_stdlib() + +try: + backend._enable_fips() +except InternalError: + pass + wait_for_db() _tmp = Path(gettempdir()) diff --git a/manage.py b/manage.py index a55ddba0fe..4c5120225a 100755 --- a/manage.py +++ b/manage.py @@ -4,6 +4,8 @@ import os import sys import warnings +from cryptography.exceptions import InternalError +from cryptography.hazmat.backends.openssl.backend import backend from defusedxml import defuse_stdlib from django.utils.autoreload import DJANGO_AUTORELOAD_ENV @@ -22,6 +24,12 @@ warnings.filterwarnings( defuse_stdlib() +try: + backend._enable_fips() +except InternalError: + pass + + if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") wait_for_db() diff --git a/proxy.Dockerfile b/proxy.Dockerfile index 61e0e5abaf..f1ad9d459b 100644 --- a/proxy.Dockerfile +++ b/proxy.Dockerfile @@ -17,7 +17,7 @@ COPY web . RUN npm run build-proxy # Stage 2: Build -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22.3-bookworm AS builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS builder ARG TARGETOS ARG TARGETARCH @@ -28,20 +28,26 @@ ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \ --mount=type=cache,target=/go/pkg/mod \ go mod download -ENV CGO_ENABLED=0 COPY . . RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ - GOARM="${TARGETVARIANT#v}" go build -o /go/proxy ./cmd/proxy + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ + go build -o /go/proxy ./cmd/proxy # Stage 3: Run -FROM gcr.io/distroless/static-debian11:debug +FROM ghcr.io/goauthentik/fips-debian:bookworm-slim-fips ARG GIT_BUILD_HASH ENV GIT_BUILD_HASH=$GIT_BUILD_HASH @@ -64,4 +70,6 @@ EXPOSE 9000 9300 9443 USER 1000 +ENV GOFIPS=1 + ENTRYPOINT ["/proxy"] diff --git a/rac.Dockerfile b/rac.Dockerfile index fd2b76eac7..2ce3d9d7ed 100644 --- a/rac.Dockerfile +++ b/rac.Dockerfile @@ -1,24 +1,37 @@ # syntax=docker/dockerfile:1 # Stage 1: Build -FROM docker.io/golang:1.22.3-bookworm AS builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS builder + +ARG TARGETOS +ARG TARGETARCH +ARG TARGETVARIANT + +ARG GOOS=$TARGETOS +ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \ --mount=type=cache,target=/go/pkg/mod \ go mod download -ENV CGO_ENABLED=0 COPY . . RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ go build -o /go/rac ./cmd/rac # Stage 2: Run -FROM ghcr.io/beryju/guacd:1.5.5 +FROM ghcr.io/beryju/guacd:1.5.5-fips ARG GIT_BUILD_HASH ENV GIT_BUILD_HASH=$GIT_BUILD_HASH @@ -35,4 +48,6 @@ HEALTHCHECK --interval=5s --retries=20 --start-period=3s CMD [ "/rac", "healthch USER 1000 +ENV GOFIPS=1 + ENTRYPOINT ["/rac"] diff --git a/radius.Dockerfile b/radius.Dockerfile index be35516a11..f1e0f8dceb 100644 --- a/radius.Dockerfile +++ b/radius.Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # Stage 1: Build -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22.3-bookworm AS builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS builder ARG TARGETOS ARG TARGETARCH @@ -12,20 +12,26 @@ ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=bind,target=/go/src/goauthentik.io/gen-go-api,src=./gen-go-api \ --mount=type=cache,target=/go/pkg/mod \ go mod download -ENV CGO_ENABLED=0 COPY . . RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ - GOARM="${TARGETVARIANT#v}" go build -o /go/radius ./cmd/radius + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ + go build -o /go/radius ./cmd/radius # Stage 2: Run -FROM gcr.io/distroless/static-debian11:debug +FROM ghcr.io/goauthentik/fips-debian:bookworm-slim-fips ARG GIT_BUILD_HASH ENV GIT_BUILD_HASH=$GIT_BUILD_HASH @@ -44,4 +50,6 @@ EXPOSE 1812/udp 9300 USER 1000 +ENV GOFIPS=1 + ENTRYPOINT ["/radius"] diff --git a/schema.yml b/schema.yml index 7b017f79c3..5c77fa0f7e 100644 --- a/schema.yml +++ b/schema.yml @@ -39368,6 +39368,18 @@ components: version: type: string readOnly: true + golang_version: + type: string + readOnly: true + openssl_enabled: + type: boolean + readOnly: true + openssl_version: + type: string + readOnly: true + fips_enabled: + type: boolean + readOnly: true version_should: type: string readOnly: true @@ -39386,8 +39398,12 @@ components: required: - build_hash - build_hash_should + - fips_enabled + - golang_version - hostname - last_seen + - openssl_enabled + - openssl_version - uid - version - version_outdated @@ -47106,8 +47122,6 @@ components: properties: python_version: type: string - gunicorn_version: - type: string environment: type: string architecture: @@ -47116,10 +47130,18 @@ components: type: string uname: type: string + openssl_version: + type: string + openssl_fips_mode: + type: boolean + authentik_version: + type: string required: - architecture + - authentik_version - environment - - gunicorn_version + - openssl_fips_mode + - openssl_version - platform - python_version - uname diff --git a/web/src/admin/outposts/OutpostHealth.ts b/web/src/admin/outposts/OutpostHealth.ts index 1a97101751..2b0203748e 100644 --- a/web/src/admin/outposts/OutpostHealth.ts +++ b/web/src/admin/outposts/OutpostHealth.ts @@ -34,10 +34,12 @@ export class OutpostHealthElement extends AKElement { } let versionString = this.outpostHealth.version; if (this.outpostHealth.buildHash) { - versionString = `${versionString} (build ${this.outpostHealth.buildHash.substring( - 0, - 8, - )})`; + versionString = msg( + str`${versionString} (build ${this.outpostHealth.buildHash.substring(0, 8)})`, + ); + } + if (this.outpostHealth.fipsEnabled) { + versionString = msg(str`${versionString} (FIPS)`); } return html`