CD/CD consolidation

This commit is contained in:
StellaOps Bot
2025-12-26 17:32:23 +02:00
parent a866eb6277
commit c786faae84
638 changed files with 3821 additions and 181 deletions

150
devops/docker/Dockerfile.ci Normal file
View File

@@ -0,0 +1,150 @@
# Dockerfile.ci - Local CI testing container matching Gitea runner environment
# Sprint: SPRINT_20251226_006_CICD
#
# Usage:
# docker build -t stellaops-ci:local -f devops/docker/Dockerfile.ci .
# docker run --rm -v $(pwd):/src stellaops-ci:local ./devops/scripts/test-local.sh
FROM ubuntu:22.04
LABEL org.opencontainers.image.title="StellaOps CI"
LABEL org.opencontainers.image.description="Local CI testing environment matching Gitea runner"
LABEL org.opencontainers.image.source="https://git.stella-ops.org/stella-ops.org/git.stella-ops.org"
# Environment variables
ENV DEBIAN_FRONTEND=noninteractive
ENV DOTNET_VERSION=10.0.100
ENV NODE_VERSION=20
ENV HELM_VERSION=3.16.0
ENV COSIGN_VERSION=2.2.4
ENV TZ=UTC
# Disable .NET telemetry
ENV DOTNET_NOLOGO=1
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
# .NET paths
ENV DOTNET_ROOT=/usr/share/dotnet
ENV PATH="/usr/share/dotnet:/root/.dotnet/tools:${PATH}"
# ===========================================================================
# BASE DEPENDENCIES
# ===========================================================================
RUN apt-get update && apt-get install -y --no-install-recommends \
# Core utilities
curl \
wget \
gnupg2 \
ca-certificates \
git \
unzip \
jq \
# Build tools
build-essential \
# Docker CLI (for DinD scenarios)
docker.io \
docker-compose-plugin \
# Cross-compilation
binutils-aarch64-linux-gnu \
# Python (for scripts)
python3 \
python3-pip \
# Locales
locales \
&& rm -rf /var/lib/apt/lists/*
# Set locale
RUN locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
# ===========================================================================
# POSTGRESQL CLIENT 16
# ===========================================================================
RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/postgresql-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends postgresql-client-16 \
&& rm -rf /var/lib/apt/lists/*
# ===========================================================================
# .NET 10 SDK
# ===========================================================================
RUN curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
&& chmod +x /tmp/dotnet-install.sh \
&& /tmp/dotnet-install.sh --version ${DOTNET_VERSION} --install-dir /usr/share/dotnet \
&& rm /tmp/dotnet-install.sh \
&& dotnet --version
# Install common .NET tools
RUN dotnet tool install -g trx2junit \
&& dotnet tool install -g dotnet-reportgenerator-globaltool
# ===========================================================================
# NODE.JS 20
# ===========================================================================
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& node --version \
&& npm --version
# ===========================================================================
# HELM 3.16.0
# ===========================================================================
RUN curl -fsSL https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz | \
tar -xzf - -C /tmp \
&& mv /tmp/linux-amd64/helm /usr/local/bin/helm \
&& rm -rf /tmp/linux-amd64 \
&& helm version
# ===========================================================================
# COSIGN
# ===========================================================================
RUN curl -fsSL https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64 \
-o /usr/local/bin/cosign \
&& chmod +x /usr/local/bin/cosign \
&& cosign version
# ===========================================================================
# SYFT (SBOM generation)
# ===========================================================================
RUN curl -fsSL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# ===========================================================================
# SETUP
# ===========================================================================
WORKDIR /src
# Create non-root user for safer execution (optional)
RUN useradd -m -s /bin/bash ciuser \
&& mkdir -p /home/ciuser/.dotnet/tools \
&& chown -R ciuser:ciuser /home/ciuser
# Health check script
COPY --chmod=755 <<'EOF' /usr/local/bin/ci-health-check
#!/bin/bash
set -e
echo "=== CI Environment Health Check ==="
echo "OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2)"
echo ".NET: $(dotnet --version)"
echo "Node: $(node --version)"
echo "npm: $(npm --version)"
echo "Helm: $(helm version --short)"
echo "Cosign: $(cosign version 2>&1 | head -1)"
echo "Docker: $(docker --version 2>/dev/null || echo 'Not available')"
echo "PostgreSQL client: $(psql --version)"
echo "=== All checks passed ==="
EOF
ENTRYPOINT ["/bin/bash"]

View File

@@ -0,0 +1,40 @@
# syntax=docker/dockerfile:1.7
# Multi-stage Angular console image with non-root runtime (DOCKER-44-001)
ARG NODE_IMAGE=node:20-bullseye-slim
ARG NGINX_IMAGE=nginxinc/nginx-unprivileged:1.27-alpine
ARG APP_DIR=src/Web/StellaOps.Web
ARG DIST_DIR=dist
ARG APP_PORT=8080
FROM ${NODE_IMAGE} AS build
ENV npm_config_fund=false npm_config_audit=false SOURCE_DATE_EPOCH=1704067200
WORKDIR /app
COPY ${APP_DIR}/package*.json ./
RUN npm ci --prefer-offline --no-progress --cache .npm
COPY ${APP_DIR}/ ./
RUN npm run build -- --configuration=production --output-path=${DIST_DIR}
FROM ${NGINX_IMAGE} AS runtime
ARG APP_PORT
ENV APP_PORT=${APP_PORT}
USER 101
WORKDIR /
COPY --from=build /app/${DIST_DIR}/ /usr/share/nginx/html/
COPY ops/devops/docker/healthcheck-frontend.sh /usr/local/bin/healthcheck-frontend.sh
RUN rm -f /etc/nginx/conf.d/default.conf && \
cat > /etc/nginx/conf.d/default.conf <<CONF
server {
listen ${APP_PORT};
listen [::]:${APP_PORT};
server_name _;
root /usr/share/nginx/html;
location / {
try_files $$uri $$uri/ /index.html;
}
}
CONF
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD /usr/local/bin/healthcheck-frontend.sh
CMD ["nginx","-g","daemon off;"]

View File

@@ -0,0 +1,172 @@
# syntax=docker/dockerfile:1.4
# StellaOps Regional Crypto Profile
# Selects regional cryptographic configuration at build time
# ============================================================================
# Build Arguments
# ============================================================================
ARG CRYPTO_PROFILE=international
ARG BASE_IMAGE=stellaops/platform:latest
ARG SERVICE_NAME=authority
# ============================================================================
# Regional Crypto Profile Layer
# ============================================================================
FROM ${BASE_IMAGE} AS regional-profile
# Copy regional cryptographic configuration
ARG CRYPTO_PROFILE
COPY etc/appsettings.crypto.${CRYPTO_PROFILE}.yaml /app/etc/appsettings.crypto.yaml
COPY etc/crypto-plugins-manifest.json /app/etc/crypto-plugins-manifest.json
# Set environment variable for runtime verification
ENV STELLAOPS_CRYPTO_PROFILE=${CRYPTO_PROFILE}
ENV STELLAOPS_CRYPTO_CONFIG_PATH=/app/etc/appsettings.crypto.yaml
ENV STELLAOPS_CRYPTO_MANIFEST_PATH=/app/etc/crypto-plugins-manifest.json
# Add labels for metadata
LABEL com.stellaops.crypto.profile="${CRYPTO_PROFILE}"
LABEL com.stellaops.crypto.config="/app/etc/appsettings.crypto.${CRYPTO_PROFILE}.yaml"
LABEL com.stellaops.crypto.runtime-selection="true"
# ============================================================================
# Service-Specific Regional Images
# ============================================================================
# Authority with Regional Crypto
FROM regional-profile AS authority
WORKDIR /app/authority
ENTRYPOINT ["dotnet", "StellaOps.Authority.WebService.dll"]
# Signer with Regional Crypto
FROM regional-profile AS signer
WORKDIR /app/signer
ENTRYPOINT ["dotnet", "StellaOps.Signer.WebService.dll"]
# Attestor with Regional Crypto
FROM regional-profile AS attestor
WORKDIR /app/attestor
ENTRYPOINT ["dotnet", "StellaOps.Attestor.WebService.dll"]
# Concelier with Regional Crypto
FROM regional-profile AS concelier
WORKDIR /app/concelier
ENTRYPOINT ["dotnet", "StellaOps.Concelier.WebService.dll"]
# Scanner with Regional Crypto
FROM regional-profile AS scanner
WORKDIR /app/scanner
ENTRYPOINT ["dotnet", "StellaOps.Scanner.WebService.dll"]
# Excititor with Regional Crypto
FROM regional-profile AS excititor
WORKDIR /app/excititor
ENTRYPOINT ["dotnet", "StellaOps.Excititor.WebService.dll"]
# Policy with Regional Crypto
FROM regional-profile AS policy
WORKDIR /app/policy
ENTRYPOINT ["dotnet", "StellaOps.Policy.WebService.dll"]
# Scheduler with Regional Crypto
FROM regional-profile AS scheduler
WORKDIR /app/scheduler
ENTRYPOINT ["dotnet", "StellaOps.Scheduler.WebService.dll"]
# Notify with Regional Crypto
FROM regional-profile AS notify
WORKDIR /app/notify
ENTRYPOINT ["dotnet", "StellaOps.Notify.WebService.dll"]
# Zastava with Regional Crypto
FROM regional-profile AS zastava
WORKDIR /app/zastava
ENTRYPOINT ["dotnet", "StellaOps.Zastava.WebService.dll"]
# Gateway with Regional Crypto
FROM regional-profile AS gateway
WORKDIR /app/gateway
ENTRYPOINT ["dotnet", "StellaOps.Gateway.WebService.dll"]
# AirGap Importer with Regional Crypto
FROM regional-profile AS airgap-importer
WORKDIR /app/airgap-importer
ENTRYPOINT ["dotnet", "StellaOps.AirGap.Importer.dll"]
# AirGap Exporter with Regional Crypto
FROM regional-profile AS airgap-exporter
WORKDIR /app/airgap-exporter
ENTRYPOINT ["dotnet", "StellaOps.AirGap.Exporter.dll"]
# CLI with Regional Crypto
FROM regional-profile AS cli
WORKDIR /app/cli
ENTRYPOINT ["dotnet", "StellaOps.Cli.dll"]
# ============================================================================
# Build Instructions
# ============================================================================
# Build international profile (default):
# docker build -f deploy/docker/Dockerfile.crypto-profile \
# --build-arg CRYPTO_PROFILE=international \
# --target authority \
# -t stellaops/authority:international .
#
# Build Russia (GOST) profile:
# docker build -f deploy/docker/Dockerfile.crypto-profile \
# --build-arg CRYPTO_PROFILE=russia \
# --target scanner \
# -t stellaops/scanner:russia .
#
# Build EU (eIDAS) profile:
# docker build -f deploy/docker/Dockerfile.crypto-profile \
# --build-arg CRYPTO_PROFILE=eu \
# --target signer \
# -t stellaops/signer:eu .
#
# Build China (SM) profile:
# docker build -f deploy/docker/Dockerfile.crypto-profile \
# --build-arg CRYPTO_PROFILE=china \
# --target attestor \
# -t stellaops/attestor:china .
#
# ============================================================================
# Regional Profile Descriptions
# ============================================================================
# international: Default NIST algorithms (ES256, RS256, SHA-256)
# Uses offline-verification plugin
# Jurisdiction: world
#
# russia: GOST R 34.10-2012, GOST R 34.11-2012
# Uses CryptoPro CSP plugin
# Jurisdiction: russia
# Requires: CryptoPro CSP SDK
#
# eu: eIDAS-compliant qualified trust services
# Uses eIDAS plugin with qualified certificates
# Jurisdiction: eu
# Requires: eIDAS trust service provider integration
#
# china: SM2, SM3, SM4 algorithms
# Uses SM crypto plugin
# Jurisdiction: china
# Requires: GmSSL or BouncyCastle SM extensions
#
# ============================================================================
# Runtime Configuration
# ============================================================================
# The crypto provider is selected at runtime based on:
# 1. STELLAOPS_CRYPTO_PROFILE environment variable
# 2. /app/etc/appsettings.crypto.yaml configuration file
# 3. /app/etc/crypto-plugins-manifest.json plugin metadata
#
# Plugin loading sequence:
# 1. Application starts
# 2. CryptoPluginLoader reads /app/etc/appsettings.crypto.yaml
# 3. Loads enabled plugins from manifest
# 4. Validates platform compatibility
# 5. Validates jurisdiction compliance
# 6. Registers providers with DI container
# 7. Application uses ICryptoProvider abstraction
#
# No cryptographic code is executed until runtime plugin selection completes.

View File

@@ -0,0 +1,56 @@
# syntax=docker/dockerfile:1.7
# Hardened multi-stage template for StellaOps services
# Parameters are build-time ARGs so this file can be re-used across services.
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim
ARG APP_PROJECT=src/Service/Service.csproj
ARG CONFIGURATION=Release
ARG PUBLISH_DIR=/app/publish
ARG APP_BINARY=StellaOps.Service
ARG APP_USER=stella
ARG APP_UID=10001
ARG APP_GID=10001
ARG APP_PORT=8080
FROM ${SDK_IMAGE} AS build
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
DOTNET_NOLOGO=1 \
SOURCE_DATE_EPOCH=1704067200
WORKDIR /src
# Expect restore sources to be available offline via local-nugets/
COPY . .
RUN dotnet restore ${APP_PROJECT} --packages /src/local-nugets && \
dotnet publish ${APP_PROJECT} -c ${CONFIGURATION} -o ${PUBLISH_DIR} \
/p:UseAppHost=true /p:PublishTrimmed=false
FROM ${RUNTIME_IMAGE} AS runtime
# Create non-root user/group with stable ids for auditability
RUN groupadd -r -g ${APP_GID} ${APP_USER} && \
useradd -r -u ${APP_UID} -g ${APP_GID} -d /var/lib/${APP_USER} ${APP_USER} && \
mkdir -p /app /var/lib/${APP_USER} /var/run/${APP_USER} /tmp && \
chown -R ${APP_UID}:${APP_GID} /app /var/lib/${APP_USER} /var/run/${APP_USER} /tmp
WORKDIR /app
COPY --from=build --chown=${APP_UID}:${APP_GID} ${PUBLISH_DIR}/ ./
# Ship healthcheck helper; callers may override with their own script
COPY --chown=${APP_UID}:${APP_GID} ops/devops/docker/healthcheck.sh /usr/local/bin/healthcheck.sh
ENV ASPNETCORE_URLS=http://+:${APP_PORT} \
DOTNET_EnableDiagnostics=0 \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
COMPlus_EnableDiagnostics=0 \
APP_BINARY=${APP_BINARY}
USER ${APP_UID}:${APP_GID}
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD /usr/local/bin/healthcheck.sh
# Harden filesystem; deploys should also set readOnlyRootFilesystem true
RUN chmod 500 /app && \
find /app -maxdepth 1 -type f -exec chmod 400 {} \; && \
find /app -maxdepth 1 -type d -exec chmod 500 {} \;
# Use shell form so APP_BINARY env can be expanded without duplicating the template per service
ENTRYPOINT ["sh","-c","exec ./\"$APP_BINARY\""]

View File

@@ -0,0 +1,212 @@
# syntax=docker/dockerfile:1.4
# StellaOps Platform Image - Build Once, Deploy Everywhere
# Builds ALL crypto plugins unconditionally for runtime selection
# ============================================================================
# Stage 1: SDK Build - Build ALL Projects and Crypto Plugins
# ============================================================================
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
WORKDIR /src
# Copy solution and project files for dependency restore
COPY Directory.Build.props Directory.Build.targets nuget.config ./
COPY src/StellaOps.sln ./src/
# Copy all crypto plugin projects
COPY src/__Libraries/StellaOps.Cryptography/ ./src/__Libraries/StellaOps.Cryptography/
COPY src/__Libraries/StellaOps.Cryptography.DependencyInjection/ ./src/__Libraries/StellaOps.Cryptography.DependencyInjection/
COPY src/__Libraries/StellaOps.Cryptography.PluginLoader/ ./src/__Libraries/StellaOps.Cryptography.PluginLoader/
# Crypto plugins - ALL built unconditionally
COPY src/__Libraries/StellaOps.Cryptography.Plugin.OfflineVerification/ ./src/__Libraries/StellaOps.Cryptography.Plugin.OfflineVerification/
# Note: Additional crypto plugins can be added here when available:
# COPY src/__Libraries/StellaOps.Cryptography.Plugin.eIDAS/ ./src/__Libraries/StellaOps.Cryptography.Plugin.eIDAS/
# COPY src/__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/ ./src/__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/
# COPY src/__Libraries/StellaOps.Cryptography.Plugin.SM/ ./src/__Libraries/StellaOps.Cryptography.Plugin.SM/
# Copy all module projects
COPY src/Authority/ ./src/Authority/
COPY src/Signer/ ./src/Signer/
COPY src/Attestor/ ./src/Attestor/
COPY src/Concelier/ ./src/Concelier/
COPY src/Scanner/ ./src/Scanner/
COPY src/AirGap/ ./src/AirGap/
COPY src/Excititor/ ./src/Excititor/
COPY src/Policy/ ./src/Policy/
COPY src/Scheduler/ ./src/Scheduler/
COPY src/Notify/ ./src/Notify/
COPY src/Zastava/ ./src/Zastava/
COPY src/Gateway/ ./src/Gateway/
COPY src/Cli/ ./src/Cli/
# Copy shared libraries
COPY src/__Libraries/ ./src/__Libraries/
# Restore dependencies
RUN dotnet restore src/StellaOps.sln
# Build entire solution (Release configuration)
RUN dotnet build src/StellaOps.sln --configuration Release --no-restore
# Publish all web services and libraries
# This creates /app/publish with all assemblies including crypto plugins
RUN dotnet publish src/Authority/StellaOps.Authority.WebService/StellaOps.Authority.WebService.csproj \
--configuration Release --no-build --output /app/publish/authority
RUN dotnet publish src/Signer/StellaOps.Signer.WebService/StellaOps.Signer.WebService.csproj \
--configuration Release --no-build --output /app/publish/signer
RUN dotnet publish src/Attestor/StellaOps.Attestor.WebService/StellaOps.Attestor.WebService.csproj \
--configuration Release --no-build --output /app/publish/attestor
RUN dotnet publish src/Concelier/StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj \
--configuration Release --no-build --output /app/publish/concelier
RUN dotnet publish src/Scanner/StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj \
--configuration Release --no-build --output /app/publish/scanner
RUN dotnet publish src/Excititor/StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj \
--configuration Release --no-build --output /app/publish/excititor
RUN dotnet publish src/Policy/StellaOps.Policy.WebService/StellaOps.Policy.WebService.csproj \
--configuration Release --no-build --output /app/publish/policy
RUN dotnet publish src/Scheduler/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj \
--configuration Release --no-build --output /app/publish/scheduler
RUN dotnet publish src/Notify/StellaOps.Notify.WebService/StellaOps.Notify.WebService.csproj \
--configuration Release --no-build --output /app/publish/notify
RUN dotnet publish src/Zastava/StellaOps.Zastava.WebService/StellaOps.Zastava.WebService.csproj \
--configuration Release --no-build --output /app/publish/zastava
RUN dotnet publish src/Gateway/StellaOps.Gateway.WebService/StellaOps.Gateway.WebService.csproj \
--configuration Release --no-build --output /app/publish/gateway
RUN dotnet publish src/AirGap/StellaOps.AirGap.Importer/StellaOps.AirGap.Importer.csproj \
--configuration Release --no-build --output /app/publish/airgap-importer
RUN dotnet publish src/AirGap/StellaOps.AirGap.Exporter/StellaOps.AirGap.Exporter.csproj \
--configuration Release --no-build --output /app/publish/airgap-exporter
RUN dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
--configuration Release --no-build --output /app/publish/cli
# Copy crypto plugin manifest
COPY etc/crypto-plugins-manifest.json /app/publish/etc/
# ============================================================================
# Stage 2: Runtime Base - Contains ALL Crypto Plugins
# ============================================================================
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview AS runtime-base
WORKDIR /app
# Install dependencies for crypto providers
# PostgreSQL client for Authority/Concelier/etc
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Copy all published assemblies (includes all crypto plugins)
COPY --from=build /app/publish /app/
# Expose common ports (these can be overridden by docker-compose)
EXPOSE 8080 8443
# Labels
LABEL com.stellaops.image.type="platform"
LABEL com.stellaops.image.variant="all-plugins"
LABEL com.stellaops.crypto.plugins="offline-verification"
# Additional plugins will be added as they become available:
# LABEL com.stellaops.crypto.plugins="offline-verification,eidas,cryptopro,sm"
# Health check placeholder (can be overridden per service)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# ============================================================================
# Service-Specific Final Stages
# ============================================================================
# Authority Service
FROM runtime-base AS authority
WORKDIR /app/authority
ENTRYPOINT ["dotnet", "StellaOps.Authority.WebService.dll"]
# Signer Service
FROM runtime-base AS signer
WORKDIR /app/signer
ENTRYPOINT ["dotnet", "StellaOps.Signer.WebService.dll"]
# Attestor Service
FROM runtime-base AS attestor
WORKDIR /app/attestor
ENTRYPOINT ["dotnet", "StellaOps.Attestor.WebService.dll"]
# Concelier Service
FROM runtime-base AS concelier
WORKDIR /app/concelier
ENTRYPOINT ["dotnet", "StellaOps.Concelier.WebService.dll"]
# Scanner Service
FROM runtime-base AS scanner
WORKDIR /app/scanner
ENTRYPOINT ["dotnet", "StellaOps.Scanner.WebService.dll"]
# Excititor Service
FROM runtime-base AS excititor
WORKDIR /app/excititor
ENTRYPOINT ["dotnet", "StellaOps.Excititor.WebService.dll"]
# Policy Service
FROM runtime-base AS policy
WORKDIR /app/policy
ENTRYPOINT ["dotnet", "StellaOps.Policy.WebService.dll"]
# Scheduler Service
FROM runtime-base AS scheduler
WORKDIR /app/scheduler
ENTRYPOINT ["dotnet", "StellaOps.Scheduler.WebService.dll"]
# Notify Service
FROM runtime-base AS notify
WORKDIR /app/notify
ENTRYPOINT ["dotnet", "StellaOps.Notify.WebService.dll"]
# Zastava Service
FROM runtime-base AS zastava
WORKDIR /app/zastava
ENTRYPOINT ["dotnet", "StellaOps.Zastava.WebService.dll"]
# Gateway Service
FROM runtime-base AS gateway
WORKDIR /app/gateway
ENTRYPOINT ["dotnet", "StellaOps.Gateway.WebService.dll"]
# AirGap Importer (CLI tool)
FROM runtime-base AS airgap-importer
WORKDIR /app/airgap-importer
ENTRYPOINT ["dotnet", "StellaOps.AirGap.Importer.dll"]
# AirGap Exporter (CLI tool)
FROM runtime-base AS airgap-exporter
WORKDIR /app/airgap-exporter
ENTRYPOINT ["dotnet", "StellaOps.AirGap.Exporter.dll"]
# CLI Tool
FROM runtime-base AS cli
WORKDIR /app/cli
ENTRYPOINT ["dotnet", "StellaOps.Cli.dll"]
# ============================================================================
# Build Instructions
# ============================================================================
# Build platform image:
# docker build -f deploy/docker/Dockerfile.platform --target runtime-base -t stellaops/platform:latest .
#
# Build specific service:
# docker build -f deploy/docker/Dockerfile.platform --target authority -t stellaops/authority:latest .
# docker build -f deploy/docker/Dockerfile.platform --target scanner -t stellaops/scanner:latest .
#
# The platform image contains ALL crypto plugins.
# Regional selection happens at runtime via configuration (see Dockerfile.crypto-profile).

View File

@@ -0,0 +1,76 @@
# Docker hardening blueprint (DOCKER-44-001)
Use this template for core services (API, Console, Orchestrator, Task Runner, Concelier, Excititor, Policy, Notify, Export, AdvisoryAI).
The reusable multi-stage scaffold lives at `ops/devops/docker/Dockerfile.hardened.template` and expects:
- .NET 10 SDK/runtime images provided via offline mirror (`SDK_IMAGE` / `RUNTIME_IMAGE`).
- `APP_PROJECT` path to the service csproj.
- `healthcheck.sh` copied from `ops/devops/docker/` (already referenced by the template).
- Optional: `APP_BINARY` (assembly name, defaults to `StellaOps.Service`) and `APP_PORT`.
Copy the template next to the service and set build args in CI (per-service matrix) to avoid maintaining divergent Dockerfiles.
```Dockerfile
# syntax=docker/dockerfile:1.7
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim
ARG APP_PROJECT=src/Service/Service.csproj
ARG CONFIGURATION=Release
ARG APP_USER=stella
ARG APP_UID=10001
ARG APP_GID=10001
ARG APP_PORT=8080
FROM ${SDK_IMAGE} AS build
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 SOURCE_DATE_EPOCH=1704067200
WORKDIR /src
COPY . .
RUN dotnet restore ${APP_PROJECT} --packages /src/local-nugets && \
dotnet publish ${APP_PROJECT} -c ${CONFIGURATION} -o /app/publish /p:UseAppHost=true /p:PublishTrimmed=false
FROM ${RUNTIME_IMAGE} AS runtime
RUN groupadd -r -g ${APP_GID} ${APP_USER} && \
useradd -r -u ${APP_UID} -g ${APP_GID} -d /var/lib/${APP_USER} ${APP_USER}
WORKDIR /app
COPY --from=build --chown=${APP_UID}:${APP_GID} /app/publish/ ./
COPY --chown=${APP_UID}:${APP_GID} ops/devops/docker/healthcheck.sh /usr/local/bin/healthcheck.sh
ENV ASPNETCORE_URLS=http://+:${APP_PORT} \
DOTNET_EnableDiagnostics=0 \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
COMPlus_EnableDiagnostics=0
USER ${APP_UID}:${APP_GID}
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 CMD /usr/local/bin/healthcheck.sh
RUN chmod 500 /app && find /app -maxdepth 1 -type f -exec chmod 400 {} \; && find /app -maxdepth 1 -type d -exec chmod 500 {} \;
ENTRYPOINT ["sh","-c","exec ./\"$APP_BINARY\""]
```
Build stage (per service) should:
- Use `mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim` (or mirror) with `DOTNET_CLI_TELEMETRY_OPTOUT=1`.
- Restore from `local-nugets/` (offline) and run `dotnet publish -c Release -o /app/out`.
- Set `SOURCE_DATE_EPOCH` to freeze timestamps.
Required checks:
- No `root` user in final image.
- `CAP_NET_RAW` dropped (default with non-root).
- Read-only rootfs enforced at deploy time (`securityContext.readOnlyRootFilesystem: true` in Helm/Compose).
- Health endpoints exposed: `/health/liveness`, `/health/readiness`, `/version`, `/metrics`.
- Image SBOM generated (syft) in pipeline; attach cosign attestations (see DOCKER-44-002).
Service matrix & helper:
- Build args for the core services are enumerated in `ops/devops/docker/services-matrix.env` (API, Console, Orchestrator, Task Runner, Concelier, Excititor, Policy, Notify, Export, AdvisoryAI).
- `ops/devops/docker/build-all.sh` reads the matrix and builds/tag images from the shared template with consistent non-root/health defaults. Override `REGISTRY` and `TAG_SUFFIX` to publish.
Console (Angular) image:
- Use `ops/devops/docker/Dockerfile.console` for the UI (Angular v17). It builds with `node:20-bullseye-slim`, serves via `nginxinc/nginx-unprivileged`, includes `healthcheck-frontend.sh`, and runs as non-root UID 101. Build with `docker build -f ops/devops/docker/Dockerfile.console --build-arg APP_DIR=src/Web/StellaOps.Web .`.
SBOM & attestation helper (DOCKER-44-002):
- Script: `ops/devops/docker/sbom_attest.sh <image> [out-dir] [cosign-key]`
- Emits SPDX (`*.spdx.json`) and CycloneDX (`*.cdx.json`) with `SOURCE_DATE_EPOCH` pinned for reproducibility.
- Attaches both as cosign attestations (`--type spdx` / `--type cyclonedx`); supports keyless when `COSIGN_EXPERIMENTAL=1` or explicit PEM key.
- Integrate in CI after image build/push; keep registry creds offline-friendly (use local registry mirror during air-gapped builds).
Health endpoint verification (DOCKER-44-003):
- Script: `ops/devops/docker/verify_health_endpoints.sh <image> [port]` spins container, checks `/health/liveness`, `/health/readiness`, `/version`, `/metrics`, and warns if `/capabilities.merge` is not `false` (for Concelier/Excititor).
- Run in CI after publishing the image; requires `docker` and `curl` (or `wget`).
- Endpoint contract and ASP.NET wiring examples live in `ops/devops/docker/health-endpoints.md`; service owners should copy the snippet and ensure readiness checks cover DB/cache/bus.

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Build hardened images for the core services using the shared template/matrix (DOCKER-44-001)
set -euo pipefail
ROOT=${ROOT:-"$(git rev-parse --show-toplevel)"}
MATRIX=${MATRIX:-"${ROOT}/ops/devops/docker/services-matrix.env"}
REGISTRY=${REGISTRY:-"stellaops"}
TAG_SUFFIX=${TAG_SUFFIX:-"dev"}
SDK_IMAGE=${SDK_IMAGE:-"mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim"}
RUNTIME_IMAGE=${RUNTIME_IMAGE:-"mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim"}
if [[ ! -f "${MATRIX}" ]]; then
echo "matrix file not found: ${MATRIX}" >&2
exit 1
fi
echo "Building services from ${MATRIX} -> ${REGISTRY}/<service>:${TAG_SUFFIX}" >&2
while IFS='|' read -r service dockerfile project binary port; do
[[ -z "${service}" || "${service}" =~ ^# ]] && continue
image="${REGISTRY}/${service}:${TAG_SUFFIX}"
df_path="${ROOT}/${dockerfile}"
if [[ ! -f "${df_path}" ]]; then
echo "skipping ${service}: dockerfile missing (${df_path})" >&2
continue
fi
if [[ "${dockerfile}" == *"Dockerfile.console"* ]]; then
# Angular console build uses its dedicated Dockerfile
echo "[console] ${service} -> ${image}" >&2
docker build \
-f "${df_path}" "${ROOT}" \
--build-arg APP_DIR="${project}" \
--build-arg APP_PORT="${port}" \
-t "${image}"
else
echo "[service] ${service} -> ${image}" >&2
docker build \
-f "${df_path}" "${ROOT}" \
--build-arg SDK_IMAGE="${SDK_IMAGE}" \
--build-arg RUNTIME_IMAGE="${RUNTIME_IMAGE}" \
--build-arg APP_PROJECT="${project}" \
--build-arg APP_BINARY="${binary}" \
--build-arg APP_PORT="${port}" \
-t "${image}"
fi
done < "${MATRIX}"
echo "Build complete. Remember to enforce readOnlyRootFilesystem at deploy time and run sbom_attest.sh (DOCKER-44-002)." >&2

View File

@@ -0,0 +1,44 @@
# Health & capability endpoint contract (DOCKER-44-003)
Target services: API, Console, Orchestrator, Task Runner, Concelier, Excititor, Policy, Notify, Export, AdvisoryAI.
## HTTP paths
- `GET /health/liveness` — fast, dependency-free check; returns `200` and minimal body.
- `GET /health/readiness` — may hit critical deps (DB, bus, cache); returns `503` when not ready.
- `GET /version` — static payload with `service`, `version`, `commit`, `buildTimestamp` (ISO-8601 UTC), `source` (channel).
- `GET /metrics` — Prometheus text exposition; reuse existing instrumentation.
- `GET /capabilities` — if present for Concelier/Excititor, must include `"merge": false`.
## Minimal ASP.NET 10 wiring (per service)
```csharp
var builder = WebApplication.CreateBuilder(args);
// health checks; add real checks as needed
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health/liveness", new() { Predicate = _ => false });
app.MapHealthChecks("/health/readiness");
app.MapGet("/version", () => Results.Json(new {
service = "StellaOps.Policy", // override per service
version = ThisAssembly.AssemblyInformationalVersion,
commit = ThisAssembly.Git.Commit,
buildTimestamp = ThisAssembly.Git.CommitDate.UtcDateTime,
source = Environment.GetEnvironmentVariable("STELLA_CHANNEL") ?? "edge"
}));
app.UseHttpMetrics();
app.MapMetrics();
app.Run();
```
- Ensure `ThisAssembly.*` source generators are enabled or substitute build vars.
- Keep `/health/liveness` lightweight; `/health/readiness` should test critical dependencies (Mongo, Redis, message bus) with timeouts.
- When adding `/capabilities`, explicitly emit `merge = false` for Concelier/Excititor.
## CI verification
- After publishing an image, run `ops/devops/docker/verify_health_endpoints.sh <image> [port]`.
- CI should fail if any required endpoint is missing or non-200.
## Deployment
- Helm/Compose should set `readOnlyRootFilesystem: true` and wire readiness/liveness probes to these paths/port.

View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
HOST="${HEALTH_HOST:-127.0.0.1}"
PORT="${HEALTH_PORT:-8080}"
PATH_CHECK="${HEALTH_PATH:-/}"
USER_AGENT="stellaops-frontend-healthcheck"
wget -qO- "http://${HOST}:${PORT}${PATH_CHECK}" \
--header="User-Agent: ${USER_AGENT}" \
--timeout="${HEALTH_TIMEOUT:-4}" >/dev/null

View File

@@ -0,0 +1,24 @@
#!/bin/sh
set -eu
HOST="${HEALTH_HOST:-127.0.0.1}"
PORT="${HEALTH_PORT:-8080}"
LIVENESS_PATH="${LIVENESS_PATH:-/health/liveness}"
READINESS_PATH="${READINESS_PATH:-/health/readiness}"
USER_AGENT="stellaops-healthcheck"
fetch() {
target_path="$1"
# BusyBox wget is available in Alpine; curl not assumed.
wget -qO- "http://${HOST}:${PORT}${target_path}" \
--header="User-Agent: ${USER_AGENT}" \
--timeout="${HEALTH_TIMEOUT:-4}" >/dev/null
}
fail=0
if ! fetch "$LIVENESS_PATH"; then
fail=1
fi
if ! fetch "$READINESS_PATH"; then
fail=1
fi
exit "$fail"

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Deterministic SBOM + attestation helper for DOCKER-44-002
# Usage: ./sbom_attest.sh <image-ref> [output-dir] [cosign-key]
# - image-ref: fully qualified image (e.g., ghcr.io/stellaops/policy:1.2.3)
# - output-dir: defaults to ./sbom
# - cosign-key: path to cosign key (PEM). If omitted, uses keyless if allowed (COSIGN_EXPERIMENTAL=1)
set -euo pipefail
IMAGE_REF=${1:?"image ref required"}
OUT_DIR=${2:-sbom}
COSIGN_KEY=${3:-}
mkdir -p "${OUT_DIR}"
# Normalize filename (replace / and : with _)
name_safe() {
echo "$1" | tr '/:' '__'
}
BASENAME=$(name_safe "${IMAGE_REF}")
SPDX_JSON="${OUT_DIR}/${BASENAME}.spdx.json"
CDX_JSON="${OUT_DIR}/${BASENAME}.cdx.json"
ATTESTATION="${OUT_DIR}/${BASENAME}.sbom.att"
# Freeze timestamps for reproducibility
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200}
# Generate SPDX 3.0-ish JSON (syft formats are stable and offline-friendly)
syft "${IMAGE_REF}" -o spdx-json > "${SPDX_JSON}"
# Generate CycloneDX 1.6 JSON
syft "${IMAGE_REF}" -o cyclonedx-json > "${CDX_JSON}"
# Attach SBOMs as cosign attestations (one per format)
export COSIGN_EXPERIMENTAL=${COSIGN_EXPERIMENTAL:-1}
COSIGN_ARGS=("attest" "--predicate" "${SPDX_JSON}" "--type" "spdx" "${IMAGE_REF}")
if [[ -n "${COSIGN_KEY}" ]]; then
COSIGN_ARGS+=("--key" "${COSIGN_KEY}")
fi
cosign "${COSIGN_ARGS[@]}"
COSIGN_ARGS=("attest" "--predicate" "${CDX_JSON}" "--type" "cyclonedx" "${IMAGE_REF}")
if [[ -n "${COSIGN_KEY}" ]]; then
COSIGN_ARGS+=("--key" "${COSIGN_KEY}")
fi
cosign "${COSIGN_ARGS[@]}"
echo "SBOMs written to ${SPDX_JSON} and ${CDX_JSON}" >&2
echo "Attestations pushed for ${IMAGE_REF}" >&2

View File

@@ -0,0 +1,12 @@
# service|dockerfile|project|binary|port
# Paths are relative to repo root; dockerfile is usually the shared hardened template.
api|ops/devops/docker/Dockerfile.hardened.template|src/VulnExplorer/StellaOps.VulnExplorer.Api/StellaOps.VulnExplorer.Api.csproj|StellaOps.VulnExplorer.Api|8080
orchestrator|ops/devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator.WebService/StellaOps.Orchestrator.WebService.csproj|StellaOps.Orchestrator.WebService|8080
task-runner|ops/devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator.Worker/StellaOps.Orchestrator.Worker.csproj|StellaOps.Orchestrator.Worker|8081
concelier|ops/devops/docker/Dockerfile.hardened.template|src/Concelier/StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj|StellaOps.Concelier.WebService|8080
excititor|ops/devops/docker/Dockerfile.hardened.template|src/Excititor/StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj|StellaOps.Excititor.WebService|8080
policy|ops/devops/docker/Dockerfile.hardened.template|src/Policy/StellaOps.Policy.Gateway/StellaOps.Policy.Gateway.csproj|StellaOps.Policy.Gateway|8084
notify|ops/devops/docker/Dockerfile.hardened.template|src/Notify/StellaOps.Notify.WebService/StellaOps.Notify.WebService.csproj|StellaOps.Notify.WebService|8080
export|ops/devops/docker/Dockerfile.hardened.template|src/ExportCenter/StellaOps.ExportCenter.WebService/StellaOps.ExportCenter.WebService.csproj|StellaOps.ExportCenter.WebService|8080
advisoryai|ops/devops/docker/Dockerfile.hardened.template|src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/StellaOps.AdvisoryAI.WebService.csproj|StellaOps.AdvisoryAI.WebService|8080
console|ops/devops/docker/Dockerfile.console|src/Web/StellaOps.Web|StellaOps.Web|8080

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Smoke-check /health and capability endpoints for a built image (DOCKER-44-003)
# Usage: ./verify_health_endpoints.sh <image-ref> [port]
# Requires: docker, curl or wget
set -euo pipefail
IMAGE=${1:?"image ref required"}
PORT=${2:-8080}
CONTAINER_NAME="healthcheck-$$"
TIMEOUT=30
SLEEP=1
have_curl=1
if ! command -v curl >/dev/null 2>&1; then
have_curl=0
fi
req() {
local path=$1
local url="http://127.0.0.1:${PORT}${path}"
if [[ $have_curl -eq 1 ]]; then
curl -fsS --max-time 3 "$url" >/dev/null
else
wget -qO- --timeout=3 "$url" >/dev/null
fi
}
cleanup() {
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
}
trap cleanup EXIT
echo "[info] starting container ${IMAGE} on port ${PORT}" >&2
cleanup
if ! docker run -d --rm --name "$CONTAINER_NAME" -p "${PORT}:${PORT}" "$IMAGE" >/dev/null; then
echo "[error] failed to start image ${IMAGE}" >&2
exit 1
fi
# wait for readiness
start=$(date +%s)
while true; do
if req /health/liveness 2>/dev/null; then break; fi
now=$(date +%s)
if (( now - start > TIMEOUT )); then
echo "[error] liveness endpoint did not come up in ${TIMEOUT}s" >&2
exit 1
fi
sleep $SLEEP
done
# verify endpoints
fail=0
for path in /health/liveness /health/readiness /version /metrics; do
if ! req "$path"; then
echo "[error] missing or failing ${path}" >&2
fail=1
fi
done
# capability endpoint optional; if present ensure merge=false for Concelier/Excititor
if req /capabilities 2>/dev/null; then
body="$(curl -fsS "http://127.0.0.1:${PORT}/capabilities" 2>/dev/null || true)"
if echo "$body" | grep -q '"merge"[[:space:]]*:[[:space:]]*false'; then
:
else
echo "[warn] /capabilities present but merge flag not false" >&2
fi
fi
exit $fail