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 EmptyPackages = new ReadOnlyCollection(System.Array.Empty()); private readonly ApkDatabaseParser _parser = new(); public ApkPackageAnalyzer(ILogger logger) : base(logger) { } public override string AnalyzerId => "apk"; protected override ValueTask> 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>(EmptyPackages); } using var stream = File.OpenRead(installedPath); var entries = _parser.Parse(stream, cancellationToken); var records = new List(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(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(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>(records); } }