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

View File

@@ -0,0 +1,185 @@
#!/bin/bash
# CryptoPro CSP 5.0 R3 Linux installer (deb packages)
# Uses locally provided .deb packages under /opt/cryptopro/downloads (host volume).
# No Wine dependency. Runs offline against the supplied packages only.
#
# Env:
# CRYPTOPRO_INSTALL_FROM Path to folder with .deb packages (default /opt/cryptopro/downloads)
# CRYPTOPRO_ACCEPT_EULA Must be 1 to proceed (default 0 -> hard stop with warning)
# CRYPTOPRO_SKIP_APT_FIX Set to 1 to skip `apt-get -f install` (offline strict)
# CRYPTOPRO_PACKAGE_FILTER Optional glob (e.g., "cprocsp*amd64.deb") to narrow selection
#
# Exit codes:
# 0 success; 1 missing dir/files; 2 incompatible arch; 3 EULA not accepted.
set -euo pipefail
INSTALL_FROM="${CRYPTOPRO_INSTALL_FROM:-/opt/cryptopro/downloads}"
PACKAGE_FILTER="${CRYPTOPRO_PACKAGE_FILTER:-*.deb}"
SKIP_APT_FIX="${CRYPTOPRO_SKIP_APT_FIX:-0}"
STAGING_DIR="/tmp/cryptopro-debs"
MINIMAL="${CRYPTOPRO_MINIMAL:-1}"
INCLUDE_PLUGIN="${CRYPTOPRO_INCLUDE_PLUGIN:-0}"
arch_from_uname() {
local raw
raw="$(uname -m)"
case "${raw}" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
arm64) echo "arm64" ;;
i386|i686) echo "i386" ;;
*) echo "${raw}" ;;
esac
}
HOST_ARCH="$(dpkg --print-architecture 2>/dev/null || arch_from_uname)"
log() {
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [cryptopro-install] $*"
}
log_err() {
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [cryptopro-install] [ERROR] $*" >&2
}
require_eula() {
if [[ "${CRYPTOPRO_ACCEPT_EULA:-0}" != "1" ]]; then
log_err "License not accepted. Set CRYPTOPRO_ACCEPT_EULA=1 only if you hold a valid CryptoPro license for these binaries and agree to the vendor EULA."
exit 3
fi
}
maybe_extract_bundle() {
# Prefer a bundle that matches host arch in filename, otherwise first *.tgz
mapfile -t TGZ < <(find "${INSTALL_FROM}" -maxdepth 1 -type f -name "*.tgz" -print 2>/dev/null | sort)
if [[ ${#TGZ[@]} -eq 0 ]]; then
return
fi
local chosen=""
for candidate in "${TGZ[@]}"; do
if [[ "${candidate}" == *"${HOST_ARCH}"* ]]; then
chosen="${candidate}"
break
fi
done
if [[ -z "${chosen}" ]]; then
chosen="${TGZ[0]}"
fi
log "Extracting bundle ${chosen} into ${STAGING_DIR}"
rm -rf "${STAGING_DIR}"
mkdir -p "${STAGING_DIR}"
tar -xf "${chosen}" -C "${STAGING_DIR}"
# If bundle contains a single subfolder, use it as install root
local subdir
subdir="$(find "${STAGING_DIR}" -maxdepth 1 -type d ! -path "${STAGING_DIR}" | head -n1)"
if [[ -n "${subdir}" ]]; then
INSTALL_FROM="${subdir}"
else
INSTALL_FROM="${STAGING_DIR}"
fi
}
gather_packages() {
if [[ ! -d "${INSTALL_FROM}" ]]; then
log_err "Package directory not found: ${INSTALL_FROM}"
exit 1
fi
maybe_extract_bundle
mapfile -t PKGS < <(find "${INSTALL_FROM}" -maxdepth 2 -type f -name "${PACKAGE_FILTER}" -print 2>/dev/null | sort)
if [[ ${#PKGS[@]} -eq 0 ]]; then
log_err "No .deb packages found in ${INSTALL_FROM} (filter=${PACKAGE_FILTER})"
exit 1
fi
}
apply_minimal_filter() {
if [[ "${MINIMAL}" != "1" ]]; then
return
fi
local -a keep_exact=(
"lsb-cprocsp-base"
"lsb-cprocsp-ca-certs"
"lsb-cprocsp-capilite-64"
"lsb-cprocsp-kc1-64"
"lsb-cprocsp-pkcs11-64"
"lsb-cprocsp-rdr-64"
"cprocsp-curl-64"
"cprocsp-pki-cades-64"
"cprocsp-compat-debian"
)
if [[ "${INCLUDE_PLUGIN}" == "1" ]]; then
keep_exact+=("cprocsp-pki-plugin-64" "cprocsp-rdr-gui-gtk-64")
fi
local -a filtered=()
for pkg in "${PKGS[@]}"; do
local name
name="$(dpkg-deb -f "${pkg}" Package 2>/dev/null || basename "${pkg}")"
for wanted in "${keep_exact[@]}"; do
if [[ "${name}" == "${wanted}" ]]; then
filtered+=("${pkg}")
break
fi
done
done
if [[ ${#filtered[@]} -gt 0 ]]; then
log "Applying minimal package set (CRYPTOPRO_MINIMAL=1); kept ${#filtered[@]} of ${#PKGS[@]}"
PKGS=("${filtered[@]}")
else
log "Minimal filter yielded no matches; using full package set"
fi
}
filter_by_arch() {
FILTERED=()
for pkg in "${PKGS[@]}"; do
local pkg_arch
pkg_arch="$(dpkg-deb -f "${pkg}" Architecture 2>/dev/null || echo "unknown")"
if [[ "${pkg_arch}" == "all" || "${pkg_arch}" == "${HOST_ARCH}" ]]; then
FILTERED+=("${pkg}")
else
log "Skipping ${pkg} (arch=${pkg_arch}, host=${HOST_ARCH})"
fi
done
if [[ ${#FILTERED[@]} -eq 0 ]]; then
log_err "No packages match host architecture ${HOST_ARCH}"
exit 2
fi
}
print_matrix() {
log "Discovered packages (arch filter: host=${HOST_ARCH}):"
for pkg in "${FILTERED[@]}"; do
local name ver arch
name="$(dpkg-deb -f "${pkg}" Package 2>/dev/null || basename "${pkg}")"
ver="$(dpkg-deb -f "${pkg}" Version 2>/dev/null || echo "unknown")"
arch="$(dpkg-deb -f "${pkg}" Architecture 2>/dev/null || echo "unknown")"
echo " - ${name} ${ver} (${arch}) <- ${pkg}"
done
}
install_packages() {
log "Installing ${#FILTERED[@]} package(s) from ${INSTALL_FROM}"
if ! dpkg -i "${FILTERED[@]}"; then
if [[ "${SKIP_APT_FIX}" == "1" ]]; then
log_err "dpkg reported errors and CRYPTOPRO_SKIP_APT_FIX=1; aborting."
exit 1
fi
log "Resolving dependencies with apt-get -f install (may require network if deps missing locally)"
apt-get update >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get -y -f install
fi
log "CryptoPro packages installed. Verify with: dpkg -l | grep cprocsp"
}
main() {
require_eula
gather_packages
apply_minimal_filter
filter_by_arch
print_matrix
install_packages
log "Installation finished. For headless/server use on Ubuntu 22.04 (amd64), the 'linux-amd64_deb.tgz' bundle is preferred and auto-selected."
}
main "$@"

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<InvariantGlobalization>true</InvariantGlobalization>
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1.7
FROM mcr.microsoft.com/dotnet/nightly/sdk:10.0 AS build
WORKDIR /src
COPY ops/cryptopro/linux-csp-service/CryptoProLinuxApi.csproj .
RUN dotnet restore CryptoProLinuxApi.csproj
COPY ops/cryptopro/linux-csp-service/ .
RUN dotnet publish CryptoProLinuxApi.csproj -c Release -r linux-x64 --self-contained true \
/p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false -o /app/publish
FROM ubuntu:22.04
ARG CRYPTOPRO_ACCEPT_EULA=0
ENV DEBIAN_FRONTEND=noninteractive \
CRYPTOPRO_ACCEPT_EULA=${CRYPTOPRO_ACCEPT_EULA} \
CRYPTOPRO_MINIMAL=1
WORKDIR /app
# System deps for CryptoPro installer
RUN apt-get update && \
apt-get install -y --no-install-recommends tar xz-utils ca-certificates && \
rm -rf /var/lib/apt/lists/*
# CryptoPro packages (provided in repo) and installer
COPY opt/cryptopro/downloads/*.tgz /opt/cryptopro/downloads/
COPY ops/cryptopro/install-linux-csp.sh /usr/local/bin/install-linux-csp.sh
RUN chmod +x /usr/local/bin/install-linux-csp.sh
# Install CryptoPro CSP (requires CRYPTOPRO_ACCEPT_EULA=1 at build/runtime)
RUN CRYPTOPRO_ACCEPT_EULA=${CRYPTOPRO_ACCEPT_EULA} /usr/local/bin/install-linux-csp.sh
# Copy published .NET app
COPY --from=build /app/publish/ /app/
EXPOSE 8080
ENTRYPOINT ["/app/CryptoProLinuxApi"]

View File

@@ -0,0 +1,118 @@
using System.Diagnostics;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.ConfigureHttpJsonOptions(opts =>
{
opts.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
var app = builder.Build();
const string CsptestPath = "/opt/cprocsp/bin/amd64/csptest";
app.MapGet("/health", () =>
{
if (!File.Exists(CsptestPath))
{
return Results.Problem(statusCode: 500, detail: "csptest not found; ensure CryptoPro CSP is installed");
}
return Results.Ok(new { status = "ok", csptest = CsptestPath });
});
app.MapGet("/license", () =>
{
var result = RunProcess([CsptestPath, "-keyset", "-info"], allowFailure: true);
return Results.Json(result);
});
app.MapPost("/hash", async (HashRequest request) =>
{
byte[] data;
try
{
data = Convert.FromBase64String(request.DataBase64);
}
catch (FormatException)
{
return Results.BadRequest(new { error = "Invalid base64" });
}
var inputPath = Path.GetTempFileName();
var outputPath = Path.GetTempFileName();
await File.WriteAllBytesAsync(inputPath, data);
var result = RunProcess([CsptestPath, "-hash", "-alg", "GOST12_256", "-in", inputPath, "-out", outputPath], allowFailure: true);
string? digestBase64 = null;
if (File.Exists(outputPath))
{
var digestBytes = await File.ReadAllBytesAsync(outputPath);
digestBase64 = Convert.ToBase64String(digestBytes);
}
TryDelete(inputPath);
TryDelete(outputPath);
return Results.Json(new
{
result.ExitCode,
result.Output,
digest_b64 = digestBase64
});
});
app.MapPost("/keyset/init", (KeysetRequest request) =>
{
var name = string.IsNullOrWhiteSpace(request.Name) ? "default" : request.Name!;
var result = RunProcess([CsptestPath, "-keyset", "-newkeyset", "-container", name, "-keytype", "none"], allowFailure: true);
return Results.Json(result);
});
app.Run("http://0.0.0.0:8080");
static void TryDelete(string path)
{
try { File.Delete(path); } catch { /* ignore */ }
}
static ProcessResult RunProcess(string[] args, bool allowFailure = false)
{
try
{
var psi = new ProcessStartInfo
{
FileName = args[0],
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
ArgumentList = { }
};
for (var i = 1; i < args.Length; i++)
{
psi.ArgumentList.Add(args[i]);
}
using var proc = Process.Start(psi)!;
var output = proc.StandardOutput.ReadToEnd();
output += proc.StandardError.ReadToEnd();
proc.WaitForExit();
if (proc.ExitCode != 0 && !allowFailure)
{
throw new InvalidOperationException($"Command failed with exit {proc.ExitCode}: {output}");
}
return new ProcessResult(proc.ExitCode, output);
}
catch (Exception ex)
{
if (!allowFailure)
{
throw;
}
return new ProcessResult(-1, ex.ToString());
}
}
sealed record HashRequest([property: JsonPropertyName("data_b64")] string DataBase64);
sealed record KeysetRequest([property: JsonPropertyName("name")] string? Name);
sealed record ProcessResult(int ExitCode, string Output);

View File

@@ -0,0 +1,33 @@
# CryptoPro Linux CSP Service (.NET minimal API)
Minimal HTTP wrapper around the Linux CryptoPro CSP binaries to prove installation and hash operations.
## Build
```bash
docker build -t cryptopro-linux-csp -f ops/cryptopro/linux-csp-service/Dockerfile .
```
`CRYPTOPRO_ACCEPT_EULA` defaults to `0` (build will fail); set to `1` only if you hold a valid CryptoPro license and accept the vendor EULA:
```bash
docker build -t cryptopro-linux-csp \
--build-arg CRYPTOPRO_ACCEPT_EULA=1 \
-f ops/cryptopro/linux-csp-service/Dockerfile .
```
## Run
```bash
docker run --rm -p 18080:8080 --name cryptopro-linux-csp-test cryptopro-linux-csp
```
Endpoints:
- `GET /health` — checks `csptest` presence.
- `GET /license` — runs `csptest -keyset -info` (reports errors if no keyset/token present).
- `POST /hash` with `{"data_b64":"<base64>"}` — hashes using `csptest -hash -alg GOST12_256`.
- `POST /keyset/init` with optional `{"name":"<container>"}` — creates an empty keyset (`-keytype none`) to silence missing-container warnings.
Notes:
- Uses the provided CryptoPro `.tgz` bundles under `opt/cryptopro/downloads`. Do not set `CRYPTOPRO_ACCEPT_EULA=1` unless you are licensed to use these binaries.
- Minimal, headless install; browser/plugin packages are not included.