up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
console-runner-image / build-runner-image (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-14 16:24:16 +02:00
parent 233873f620
commit e2e404e705
37 changed files with 2079 additions and 118 deletions

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Build console container image with SBOM and optional attestations
# Usage: ./build-console-image.sh [tag] [registry]
# Example: ./build-console-image.sh 2025.10.0-edge ghcr.io/stellaops
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
TAG="${1:-$(date +%Y%m%dT%H%M%S)}"
REGISTRY="${2:-registry.stella-ops.org/stellaops}"
IMAGE_NAME="console"
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
# Freeze timestamps for reproducibility
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200}
echo "==> Building console image: ${FULL_IMAGE}"
# Build using the existing Dockerfile.console
docker build \
--file "${REPO_ROOT}/ops/devops/docker/Dockerfile.console" \
--build-arg APP_DIR=src/Web/StellaOps.Web \
--build-arg APP_PORT=8080 \
--tag "${FULL_IMAGE}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--label "org.opencontainers.image.revision=$(git -C "${REPO_ROOT}" rev-parse HEAD 2>/dev/null || echo 'unknown')" \
--label "org.opencontainers.image.source=https://github.com/stellaops/stellaops" \
--label "org.opencontainers.image.title=StellaOps Console" \
--label "org.opencontainers.image.description=StellaOps Angular Console (non-root nginx)" \
"${REPO_ROOT}"
# Get digest
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "${FULL_IMAGE}" 2>/dev/null || echo "${FULL_IMAGE}")
echo "==> Image built: ${FULL_IMAGE}"
echo "==> Digest: ${DIGEST}"
# Output metadata for CI
mkdir -p "${SCRIPT_DIR}/../artifacts/console"
cat > "${SCRIPT_DIR}/../artifacts/console/build-metadata.json" <<EOF
{
"image": "${FULL_IMAGE}",
"digest": "${DIGEST}",
"tag": "${TAG}",
"registry": "${REGISTRY}",
"buildTime": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"gitCommit": "$(git -C "${REPO_ROOT}" rev-parse HEAD 2>/dev/null || echo 'unknown')",
"sourceDateEpoch": "${SOURCE_DATE_EPOCH}"
}
EOF
echo "==> Build metadata written to ops/devops/artifacts/console/build-metadata.json"
# Generate SBOM if syft is available
if command -v syft &>/dev/null; then
echo "==> Generating SBOM..."
syft "${FULL_IMAGE}" -o spdx-json > "${SCRIPT_DIR}/../artifacts/console/console.spdx.json"
syft "${FULL_IMAGE}" -o cyclonedx-json > "${SCRIPT_DIR}/../artifacts/console/console.cdx.json"
echo "==> SBOMs written to ops/devops/artifacts/console/"
else
echo "==> Skipping SBOM generation (syft not found)"
fi
# Sign and attest if cosign is available and key is set
if command -v cosign &>/dev/null; then
if [[ -n "${COSIGN_KEY:-}" ]]; then
echo "==> Signing image with cosign..."
cosign sign --key "${COSIGN_KEY}" "${FULL_IMAGE}"
if [[ -f "${SCRIPT_DIR}/../artifacts/console/console.spdx.json" ]]; then
echo "==> Attesting SBOM..."
cosign attest --predicate "${SCRIPT_DIR}/../artifacts/console/console.spdx.json" \
--type spdx --key "${COSIGN_KEY}" "${FULL_IMAGE}"
fi
echo "==> Image signed and attested"
else
echo "==> Skipping signing (COSIGN_KEY not set)"
fi
else
echo "==> Skipping signing (cosign not found)"
fi
echo "==> Console image build complete"
echo " Image: ${FULL_IMAGE}"

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env bash
# Package console for offline/airgap deployment
# Usage: ./package-offline-bundle.sh [image] [output-dir]
# Example: ./package-offline-bundle.sh registry.stella-ops.org/stellaops/console:2025.10.0 ./offline-bundle
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
IMAGE="${1:-registry.stella-ops.org/stellaops/console:latest}"
OUTPUT_DIR="${2:-${SCRIPT_DIR}/../artifacts/console/offline-bundle}"
BUNDLE_NAME="console-offline-$(date +%Y%m%dT%H%M%S)"
# Freeze timestamps
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200}
echo "==> Creating offline bundle for: ${IMAGE}"
mkdir -p "${OUTPUT_DIR}"
# Save image as tarball
IMAGE_TAR="${OUTPUT_DIR}/${BUNDLE_NAME}.tar"
echo "==> Saving image to ${IMAGE_TAR}..."
docker save "${IMAGE}" -o "${IMAGE_TAR}"
# Calculate checksums
echo "==> Generating checksums..."
cd "${OUTPUT_DIR}"
sha256sum "${BUNDLE_NAME}.tar" > "${BUNDLE_NAME}.tar.sha256"
# Copy Helm values
echo "==> Including Helm values overlay..."
cp "${REPO_ROOT}/deploy/helm/stellaops/values-console.yaml" "${OUTPUT_DIR}/"
# Copy Dockerfile for reference
cp "${REPO_ROOT}/ops/devops/docker/Dockerfile.console" "${OUTPUT_DIR}/"
# Generate SBOMs if syft available
if command -v syft &>/dev/null; then
echo "==> Generating SBOMs..."
syft "${IMAGE}" -o spdx-json > "${OUTPUT_DIR}/${BUNDLE_NAME}.spdx.json"
syft "${IMAGE}" -o cyclonedx-json > "${OUTPUT_DIR}/${BUNDLE_NAME}.cdx.json"
fi
# Create manifest
cat > "${OUTPUT_DIR}/manifest.json" <<EOF
{
"bundle": "${BUNDLE_NAME}",
"image": "${IMAGE}",
"imageTarball": "${BUNDLE_NAME}.tar",
"checksumFile": "${BUNDLE_NAME}.tar.sha256",
"helmValues": "values-console.yaml",
"dockerfile": "Dockerfile.console",
"sbom": {
"spdx": "${BUNDLE_NAME}.spdx.json",
"cyclonedx": "${BUNDLE_NAME}.cdx.json"
},
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"sourceDateEpoch": "${SOURCE_DATE_EPOCH}"
}
EOF
# Create load script
cat > "${OUTPUT_DIR}/load.sh" <<'LOAD'
#!/usr/bin/env bash
# Load console image into local Docker daemon
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MANIFEST="${SCRIPT_DIR}/manifest.json"
if [[ ! -f "${MANIFEST}" ]]; then
echo "ERROR: manifest.json not found" >&2
exit 1
fi
TARBALL=$(jq -r '.imageTarball' "${MANIFEST}")
CHECKSUM_FILE=$(jq -r '.checksumFile' "${MANIFEST}")
echo "==> Verifying checksum..."
cd "${SCRIPT_DIR}"
sha256sum -c "${CHECKSUM_FILE}"
echo "==> Loading image..."
docker load -i "${TARBALL}"
IMAGE=$(jq -r '.image' "${MANIFEST}")
echo "==> Image loaded: ${IMAGE}"
LOAD
chmod +x "${OUTPUT_DIR}/load.sh"
# Create README
cat > "${OUTPUT_DIR}/README.md" <<EOF
# Console Offline Bundle
This bundle contains the StellaOps Console container image and deployment assets
for air-gapped environments.
## Contents
- \`${BUNDLE_NAME}.tar\` - Docker image tarball
- \`${BUNDLE_NAME}.tar.sha256\` - SHA-256 checksum
- \`values-console.yaml\` - Helm values overlay
- \`Dockerfile.console\` - Reference Dockerfile
- \`${BUNDLE_NAME}.spdx.json\` - SPDX SBOM (if generated)
- \`${BUNDLE_NAME}.cdx.json\` - CycloneDX SBOM (if generated)
- \`manifest.json\` - Bundle manifest
- \`load.sh\` - Image load helper script
## Usage
1. Transfer this bundle to the air-gapped environment
2. Verify checksums: \`sha256sum -c ${BUNDLE_NAME}.tar.sha256\`
3. Load image: \`./load.sh\` or \`docker load -i ${BUNDLE_NAME}.tar\`
4. Deploy with Helm: \`helm install stellaops ../stellaops -f values-console.yaml\`
## Image Details
- Image: \`${IMAGE}\`
- Created: $(date -u +%Y-%m-%dT%H:%M:%SZ)
- Non-root user: UID 101 (nginx-unprivileged)
- Port: 8080
## Verification
The image runs as non-root and supports read-only root filesystem.
Enable \`readOnlyRootFilesystem: true\` in your security context.
EOF
echo "==> Offline bundle created at: ${OUTPUT_DIR}"
echo "==> Contents:"
ls -la "${OUTPUT_DIR}"

View File

@@ -2,7 +2,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/UI/StellaOps.UI
ARG APP_DIR=src/Web/StellaOps.Web
ARG DIST_DIR=dist
ARG APP_PORT=8080

View File

@@ -62,7 +62,7 @@ Service matrix & helper:
- `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/UI/StellaOps.UI .`.
- 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]`

View File

@@ -9,4 +9,4 @@ policy|ops/devops/docker/Dockerfile.hardened.template|src/Policy/StellaOps.Polic
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/UI/StellaOps.UI|StellaOps.UI|8080
console|ops/devops/docker/Dockerfile.console|src/Web/StellaOps.Web|StellaOps.Web|8080

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Package Java analyzer plugin for release/offline distribution
# Usage: ./package-analyzer.sh [version] [output-dir]
# Example: ./package-analyzer.sh 2025.10.0 ./dist
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
VERSION="${1:-$(date +%Y.%m.%d)}"
OUTPUT_DIR="${2:-${SCRIPT_DIR}/../artifacts/scanner-java}"
PROJECT_PATH="src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.csproj"
# Freeze timestamps for reproducibility
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200}
echo "==> Packaging Java analyzer v${VERSION}"
mkdir -p "${OUTPUT_DIR}"
# Build for all target RIDs
RIDS=("linux-x64" "linux-arm64" "osx-x64" "osx-arm64" "win-x64")
for RID in "${RIDS[@]}"; do
echo "==> Building for ${RID}..."
dotnet publish "${REPO_ROOT}/${PROJECT_PATH}" \
--configuration Release \
--runtime "${RID}" \
--self-contained false \
--output "${OUTPUT_DIR}/java-analyzer-${VERSION}-${RID}" \
/p:Version="${VERSION}" \
/p:PublishTrimmed=false \
/p:DebugType=None
done
# Create combined archive
ARCHIVE_NAME="scanner-java-analyzer-${VERSION}"
echo "==> Creating archive ${ARCHIVE_NAME}.tar.gz..."
cd "${OUTPUT_DIR}"
tar -czf "${ARCHIVE_NAME}.tar.gz" java-analyzer-${VERSION}-*/
# Generate checksums
echo "==> Generating checksums..."
sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
for RID in "${RIDS[@]}"; do
(cd "java-analyzer-${VERSION}-${RID}" && sha256sum *.dll *.json 2>/dev/null > ../java-analyzer-${VERSION}-${RID}.sha256 || true)
done
# Generate SBOM if syft available
if command -v syft &>/dev/null; then
echo "==> Generating SBOM..."
syft dir:"${OUTPUT_DIR}/java-analyzer-${VERSION}-linux-x64" -o spdx-json > "${OUTPUT_DIR}/${ARCHIVE_NAME}.spdx.json"
syft dir:"${OUTPUT_DIR}/java-analyzer-${VERSION}-linux-x64" -o cyclonedx-json > "${OUTPUT_DIR}/${ARCHIVE_NAME}.cdx.json"
fi
# Sign if cosign available
if command -v cosign &>/dev/null && [[ -n "${COSIGN_KEY:-}" ]]; then
echo "==> Signing archive..."
cosign sign-blob --key "${COSIGN_KEY}" "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sig"
fi
# Create manifest
cat > "${OUTPUT_DIR}/manifest.json" <<EOF
{
"analyzer": "scanner-java",
"version": "${VERSION}",
"archive": "${ARCHIVE_NAME}.tar.gz",
"checksumFile": "${ARCHIVE_NAME}.tar.gz.sha256",
"rids": $(printf '%s\n' "${RIDS[@]}" | jq -R . | jq -s .),
"sbom": {
"spdx": "${ARCHIVE_NAME}.spdx.json",
"cyclonedx": "${ARCHIVE_NAME}.cdx.json"
},
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"sourceDateEpoch": "${SOURCE_DATE_EPOCH}",
"components": [
"Maven/Gradle parsing",
"JAR/WAR/EAR analysis",
"Java callgraph builder",
"JNI native bridge detection",
"Service provider scanning",
"Shaded JAR detection"
]
}
EOF
echo "==> Java analyzer packaged to ${OUTPUT_DIR}"
echo " Archive: ${ARCHIVE_NAME}.tar.gz"
echo " RIDs: ${RIDS[*]}"

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Package Native analyzer plugin for release/offline distribution
# Usage: ./package-analyzer.sh [version] [output-dir]
# Example: ./package-analyzer.sh 2025.10.0 ./dist
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
VERSION="${1:-$(date +%Y.%m.%d)}"
OUTPUT_DIR="${2:-${SCRIPT_DIR}/../artifacts/scanner-native}"
PROJECT_PATH="src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Native/StellaOps.Scanner.Analyzers.Native.csproj"
# Freeze timestamps for reproducibility
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200}
echo "==> Packaging Native analyzer v${VERSION}"
mkdir -p "${OUTPUT_DIR}"
# Build for all target RIDs
RIDS=("linux-x64" "linux-arm64" "osx-x64" "osx-arm64" "win-x64")
for RID in "${RIDS[@]}"; do
echo "==> Building for ${RID}..."
dotnet publish "${REPO_ROOT}/${PROJECT_PATH}" \
--configuration Release \
--runtime "${RID}" \
--self-contained false \
--output "${OUTPUT_DIR}/native-analyzer-${VERSION}-${RID}" \
/p:Version="${VERSION}" \
/p:PublishTrimmed=false \
/p:DebugType=None
done
# Create combined archive
ARCHIVE_NAME="scanner-native-analyzer-${VERSION}"
echo "==> Creating archive ${ARCHIVE_NAME}.tar.gz..."
cd "${OUTPUT_DIR}"
tar -czf "${ARCHIVE_NAME}.tar.gz" native-analyzer-${VERSION}-*/
# Generate checksums
echo "==> Generating checksums..."
sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
for RID in "${RIDS[@]}"; do
(cd "native-analyzer-${VERSION}-${RID}" && sha256sum *.dll *.json 2>/dev/null > ../native-analyzer-${VERSION}-${RID}.sha256 || true)
done
# Generate SBOM if syft available
if command -v syft &>/dev/null; then
echo "==> Generating SBOM..."
syft dir:"${OUTPUT_DIR}/native-analyzer-${VERSION}-linux-x64" -o spdx-json > "${OUTPUT_DIR}/${ARCHIVE_NAME}.spdx.json"
syft dir:"${OUTPUT_DIR}/native-analyzer-${VERSION}-linux-x64" -o cyclonedx-json > "${OUTPUT_DIR}/${ARCHIVE_NAME}.cdx.json"
fi
# Sign if cosign available
if command -v cosign &>/dev/null && [[ -n "${COSIGN_KEY:-}" ]]; then
echo "==> Signing archive..."
cosign sign-blob --key "${COSIGN_KEY}" "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sig"
fi
# Create manifest
cat > "${OUTPUT_DIR}/manifest.json" <<EOF
{
"analyzer": "scanner-native",
"version": "${VERSION}",
"archive": "${ARCHIVE_NAME}.tar.gz",
"checksumFile": "${ARCHIVE_NAME}.tar.gz.sha256",
"rids": $(printf '%s\n' "${RIDS[@]}" | jq -R . | jq -s .),
"sbom": {
"spdx": "${ARCHIVE_NAME}.spdx.json",
"cyclonedx": "${ARCHIVE_NAME}.cdx.json"
},
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"sourceDateEpoch": "${SOURCE_DATE_EPOCH}",
"components": [
"ELF/PE/Mach-O parsing",
"Symbol demangling (Itanium/Rust)",
"Native callgraph builder",
"Build-ID extraction"
]
}
EOF
echo "==> Native analyzer packaged to ${OUTPUT_DIR}"
echo " Archive: ${ARCHIVE_NAME}.tar.gz"
echo " RIDs: ${RIDS[*]}"