feat: Initialize Zastava Webhook service with TLS and Authority authentication

- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
master
2025-10-19 18:36:22 +03:00
parent 2062da7a8b
commit d099a90f9b
966 changed files with 91038 additions and 1850 deletions

View File

@@ -0,0 +1,21 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Analyzers.OS.Abstractions;
using StellaOps.Scanner.Analyzers.OS.Plugin;
namespace StellaOps.Scanner.Analyzers.OS.Apk;
public sealed class ApkAnalyzerPlugin : IOSAnalyzerPlugin
{
public string Name => "StellaOps.Scanner.Analyzers.OS.Apk";
public bool IsAvailable(IServiceProvider services) => services is not null;
public IOSPackageAnalyzer CreateAnalyzer(IServiceProvider services)
{
ArgumentNullException.ThrowIfNull(services);
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
return new ApkPackageAnalyzer(loggerFactory.CreateLogger<ApkPackageAnalyzer>());
}
}

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
namespace StellaOps.Scanner.Analyzers.OS.Apk;
internal sealed class ApkDatabaseParser
{
public IReadOnlyList<ApkPackageEntry> Parse(Stream stream, CancellationToken cancellationToken)
{
var packages = new List<ApkPackageEntry>();
var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4096, leaveOpen: true);
var current = new ApkPackageEntry();
string? currentDirectory = "/";
string? pendingDigest = null;
bool pendingConfig = false;
string? line;
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(line))
{
CommitCurrent();
current = new ApkPackageEntry();
currentDirectory = "/";
pendingDigest = null;
pendingConfig = false;
continue;
}
if (line.Length < 2)
{
continue;
}
var key = line[0];
var value = line.Length > 2 ? line[2..] : string.Empty;
switch (key)
{
case 'C':
current.Channel = value;
break;
case 'P':
current.Name = value;
break;
case 'V':
current.Version = value;
break;
case 'A':
current.Architecture = value;
break;
case 'S':
current.InstalledSize = value;
break;
case 'I':
current.PackageSize = value;
break;
case 'T':
current.Description = value;
break;
case 'U':
current.Url = value;
break;
case 'L':
current.License = value;
break;
case 'o':
current.Origin = value;
break;
case 'm':
current.Maintainer = value;
break;
case 't':
current.BuildTime = value;
break;
case 'c':
current.Checksum = value;
break;
case 'D':
current.Depends.AddRange(SplitList(value));
break;
case 'p':
current.Provides.AddRange(SplitList(value));
break;
case 'F':
currentDirectory = NormalizeDirectory(value);
current.Files.Add(new ApkFileEntry(currentDirectory, true, false, null));
break;
case 'R':
if (currentDirectory is null)
{
currentDirectory = "/";
}
var fullPath = CombinePath(currentDirectory, value);
current.Files.Add(new ApkFileEntry(fullPath, false, pendingConfig, pendingDigest));
pendingDigest = null;
pendingConfig = false;
break;
case 'Z':
pendingDigest = string.IsNullOrWhiteSpace(value) ? null : value.Trim();
break;
case 'a':
pendingConfig = value.Contains("cfg", StringComparison.OrdinalIgnoreCase);
break;
default:
current.Metadata[key.ToString()] = value;
break;
}
}
CommitCurrent();
return packages;
void CommitCurrent()
{
if (!string.IsNullOrWhiteSpace(current.Name) &&
!string.IsNullOrWhiteSpace(current.Version) &&
!string.IsNullOrWhiteSpace(current.Architecture))
{
packages.Add(current);
}
}
}
private static IEnumerable<string> SplitList(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
yield break;
}
foreach (var token in value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
yield return token;
}
}
private static string NormalizeDirectory(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "/";
}
var path = value.Trim();
if (!path.StartsWith('/'))
{
path = "/" + path;
}
if (!path.EndsWith('/'))
{
path += "/";
}
return path.Replace("//", "/");
}
private static string CombinePath(string directory, string relative)
{
if (string.IsNullOrWhiteSpace(relative))
{
return directory.TrimEnd('/');
}
if (!directory.EndsWith('/'))
{
directory += "/";
}
return (directory + relative.TrimStart('/')).Replace("//", "/");
}
}
internal sealed class ApkPackageEntry
{
public string? Channel { get; set; }
public string? Name { get; set; }
public string? Version { get; set; }
public string? Architecture { get; set; }
public string? InstalledSize { get; set; }
public string? PackageSize { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
public string? License { get; set; }
public string? Origin { get; set; }
public string? Maintainer { get; set; }
public string? BuildTime { get; set; }
public string? Checksum { get; set; }
public List<string> Depends { get; } = new();
public List<string> Provides { get; } = new();
public List<ApkFileEntry> Files { get; } = new();
public Dictionary<string, string?> Metadata { get; } = new(StringComparer.Ordinal);
}
internal sealed record ApkFileEntry(string Path, bool IsDirectory, bool IsConfig, string? Digest);

View File

@@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Scanner.Analyzers.OS;
using StellaOps.Scanner.Analyzers.OS.Abstractions;
using StellaOps.Scanner.Analyzers.OS.Analyzers;
using StellaOps.Scanner.Analyzers.OS.Helpers;
namespace StellaOps.Scanner.Analyzers.OS.Apk;
internal sealed class ApkPackageAnalyzer : OsPackageAnalyzerBase
{
private static readonly IReadOnlyList<OSPackageRecord> EmptyPackages =
new ReadOnlyCollection<OSPackageRecord>(System.Array.Empty<OSPackageRecord>());
private readonly ApkDatabaseParser _parser = new();
public ApkPackageAnalyzer(ILogger<ApkPackageAnalyzer> logger)
: base(logger)
{
}
public override string AnalyzerId => "apk";
protected override ValueTask<IReadOnlyList<OSPackageRecord>> ExecuteCoreAsync(OSPackageAnalyzerContext context, CancellationToken cancellationToken)
{
var installedPath = Path.Combine(context.RootPath, "lib", "apk", "db", "installed");
if (!File.Exists(installedPath))
{
Logger.LogInformation("Apk installed database not found at {Path}; skipping analyzer.", installedPath);
return ValueTask.FromResult<IReadOnlyList<OSPackageRecord>>(EmptyPackages);
}
using var stream = File.OpenRead(installedPath);
var entries = _parser.Parse(stream, cancellationToken);
var records = new List<OSPackageRecord>(entries.Count);
foreach (var entry in entries)
{
if (string.IsNullOrWhiteSpace(entry.Name) ||
string.IsNullOrWhiteSpace(entry.Version) ||
string.IsNullOrWhiteSpace(entry.Architecture))
{
continue;
}
var versionParts = PackageVersionParser.ParseApkVersion(entry.Version);
var purl = PackageUrlBuilder.BuildAlpine(entry.Name, entry.Version, entry.Architecture);
var vendorMetadata = new Dictionary<string, string?>(StringComparer.Ordinal)
{
["origin"] = entry.Origin,
["description"] = entry.Description,
["homepage"] = entry.Url,
["maintainer"] = entry.Maintainer,
["checksum"] = entry.Checksum,
["buildTime"] = entry.BuildTime,
};
foreach (var pair in entry.Metadata)
{
vendorMetadata[$"apk:{pair.Key}"] = pair.Value;
}
var files = new List<OSPackageFileEvidence>(entry.Files.Count);
foreach (var file in entry.Files)
{
files.Add(new OSPackageFileEvidence(
file.Path,
layerDigest: null,
sha256: file.Digest,
sizeBytes: null,
isConfigFile: file.IsConfig));
}
var cveHints = CveHintExtractor.Extract(
string.Join(' ', entry.Depends),
string.Join(' ', entry.Provides));
var record = new OSPackageRecord(
AnalyzerId,
purl,
entry.Name,
versionParts.BaseVersion,
entry.Architecture,
PackageEvidenceSource.ApkDatabase,
epoch: null,
release: versionParts.Release,
sourcePackage: entry.Origin,
license: entry.License,
cveHints: cveHints,
provides: entry.Provides,
depends: entry.Depends,
files: files,
vendorMetadata: vendorMetadata);
records.Add(record);
}
records.Sort();
return ValueTask.FromResult<IReadOnlyList<OSPackageRecord>>(records);
}
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Scanner.Analyzers.OS.Tests")]

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Scanner.Analyzers.OS\StellaOps.Scanner.Analyzers.OS.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
{
"schemaVersion": "1.0",
"id": "stellaops.analyzers.os.apk",
"displayName": "StellaOps Alpine APK Analyzer",
"version": "0.1.0-alpha",
"requiresRestart": true,
"entryPoint": {
"type": "dotnet",
"assembly": "StellaOps.Scanner.Analyzers.OS.Apk.dll"
},
"capabilities": [
"os-analyzer",
"apk"
],
"metadata": {
"org.stellaops.analyzer.kind": "os",
"org.stellaops.analyzer.id": "apk"
}
}