CD/CD consolidation
This commit is contained in:
@@ -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>
|
||||
36
devops/services/cryptopro/linux-csp-service/Dockerfile
Normal file
36
devops/services/cryptopro/linux-csp-service/Dockerfile
Normal 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"]
|
||||
118
devops/services/cryptopro/linux-csp-service/Program.cs
Normal file
118
devops/services/cryptopro/linux-csp-service/Program.cs
Normal 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);
|
||||
33
devops/services/cryptopro/linux-csp-service/README.md
Normal file
33
devops/services/cryptopro/linux-csp-service/README.md
Normal 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.
|
||||
Reference in New Issue
Block a user