Resolve Concelier/Excititor merge conflicts
This commit is contained in:
21
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkAnalyzerPlugin.cs
Normal file
21
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkAnalyzerPlugin.cs
Normal 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>());
|
||||
}
|
||||
}
|
||||
203
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkDatabaseParser.cs
Normal file
203
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkDatabaseParser.cs
Normal 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);
|
||||
106
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkPackageAnalyzer.cs
Normal file
106
src/StellaOps.Scanner.Analyzers.OS.Apk/ApkPackageAnalyzer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("StellaOps.Scanner.Analyzers.OS.Tests")]
|
||||
@@ -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>
|
||||
19
src/StellaOps.Scanner.Analyzers.OS.Apk/manifest.json
Normal file
19
src/StellaOps.Scanner.Analyzers.OS.Apk/manifest.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user