/// <inheritdoc /> public async Task SaveInProgressImageScan(ImageScanResultWithCVEs imageScanResult) { Logger.Information("Saving In-Progress Image Scan {ImageScanId} for {ImageTag}", imageScanResult.Id, imageScanResult.ImageTag); this.db.Set <ImageScanResultEntity>().Add(imageScanResult.ToEntity()); await this.db.SaveChangesAsync(); }
public async Task SaveInProgressImageScanSavesCorrectEntity() { // Arrange await using var context = JosekiTestsDb.CreateUniqueContext(); var parser = new ConfigurationParser("config.sample.yaml"); var db = new MssqlJosekiDatabase(context, parser); var scan = new ImageScanResultWithCVEs { Date = DateTime.UtcNow, Id = Guid.NewGuid().ToString(), Description = Guid.NewGuid().ToString(), ImageTag = Guid.NewGuid().ToString(), Status = ImageScanStatus.Queued, }; // Act & Assert context.ImageScanResult.Should().HaveCount(0); await db.SaveInProgressImageScan(scan); context.ImageScanResult.Should().HaveCount(1); var actual = await context.ImageScanResult.FirstAsync(); actual.Date.Should().Be(scan.Date); actual.ExternalId.Should().Be(scan.Id); actual.Description.Should().Be(scan.Description); actual.ImageTag.Should().Be(scan.ImageTag); actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Queued); }
/// <inheritdoc /> public async Task EnqueueImageScanRequest(ImageScanResultWithCVEs imageScan) { Logger.Information("Enqueueing Image {ImageTag} Scan request", imageScan.ImageTag); try { var message = new ImageScanRequestMessage { Headers = new MessageHeaders { CreationTime = DateTimeOffset.Now.ToUnixTimeSeconds(), PayloadVersion = ImageScanRequestPayload.VERSION, }, Payload = new ImageScanRequestPayload { ImageFullName = imageScan.ImageTag, ImageScanId = imageScan.Id, }, }; var messageJson = JsonConvert.SerializeObject(message, Formatting.None); var bytes = System.Text.Encoding.UTF8.GetBytes(messageJson); var base64String = Convert.ToBase64String(bytes); var response = await this.imageScanQueue.SendMessageAsync(base64String, timeToLive : TimeSpan.FromDays(7)); Logger.Information( "Image {ImageTag} Scan request was queued. Message identifier: {QueueMessageId}", imageScan.ImageTag, response.Value.MessageId); } catch (Exception ex) { Logger.Warning(ex, "Image {ImageTag} Scan request failed to be queued", imageScan.ImageTag); } }
private static bool VerifyFailedScan(ImageScanResultWithCVEs scanResult) { var scanDate = DateTimeOffset.FromUnixTimeSeconds(1583894802).DateTime; scanResult.Id.Should().Be("1abcd181-e4ac-43a6-94c0-47f06bb3311e"); scanResult.Date.Should().BeCloseTo(scanDate, TimeSpan.FromMinutes(1)); scanResult.ImageTag.Should().Be("registry.com/repository/failed-image:v0.2"); scanResult.Status.Should().Be(ImageScanStatus.Failed); scanResult.Description.Should().Be(TrivyScanDescriptionNormalizer.UnknownOS); return(true); }
public async Task SaveImageScanResultCouldSaveNewEntity() { // Arrange await using var context = JosekiTestsDb.CreateUniqueContext(); var parser = new ConfigurationParser("config.sample.yaml"); var db = new MssqlJosekiDatabase(context, parser); var cves = new List <ImageScanToCve> { new ImageScanToCve { InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, }; var scan = new ImageScanResultWithCVEs { Date = DateTime.UtcNow, Id = Guid.NewGuid().ToString(), Description = Guid.NewGuid().ToString(), ImageTag = Guid.NewGuid().ToString(), Status = ImageScanStatus.Succeeded, FoundCVEs = cves, }; // Act & Assert context.ImageScanResult.Should().HaveCount(0); context.ImageScanResultToCve.Should().HaveCount(0); await db.SaveImageScanResult(scan); context.ImageScanResult.Should().HaveCount(1); context.ImageScanResultToCve.Should().HaveCount(cves.Count); var actual = await context.ImageScanResult.FirstAsync(); actual.Date.Should().Be(scan.Date); actual.ExternalId.Should().Be(scan.Id); actual.Description.Should().Be(scan.Description); actual.ImageTag.Should().Be(scan.ImageTag); actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Succeeded); foreach (var actualCve in await context.ImageScanResultToCve.ToArrayAsync()) { var expectedCve = cves.First(i => i.InternalCveId == actualCve.CveId); actualCve.Target.Should().Be(expectedCve.Target); actualCve.UsedPackage.Should().Be(expectedCve.UsedPackage); actualCve.UsedPackageVersion.Should().Be(expectedCve.UsedPackageVersion); } }
private static bool VerifyNoCveScan(ImageScanResultWithCVEs scanResult) { var scanDate = DateTimeOffset.FromUnixTimeSeconds(1583894783).DateTime; scanResult.Id.Should().Be("44440604-22b6-4a8b-b1f7-0faddd20c37d"); scanResult.Date.Should().BeCloseTo(scanDate, TimeSpan.FromMinutes(1)); scanResult.ImageTag.Should().Be("registry.com/repository/no-cve-image:v0.2"); scanResult.Status.Should().Be(ImageScanStatus.Succeeded); scanResult.Description.Should().BeNullOrEmpty(); scanResult.FoundCVEs.Should().HaveCount(0); scanResult.Counters.Should().HaveCount(0); return(true); }
/// <summary> /// Creates Image Scan entity from internal model. /// </summary> /// <param name="scan">Internal Image Scan model.</param> /// <returns>Database compatible entity.</returns> public static ImageScanResultEntity ToEntity(this ImageScanResultWithCVEs scan) { var entity = new ImageScanResultEntity { ExternalId = scan.Id, ImageTag = scan.ImageTag, Date = scan.Date, Status = scan.Status.ToEntity(), Description = scan.Description, FoundCVEs = scan.FoundCVEs?.Select(i => i.ToEntity()).ToList(), }; return(entity); }
private static bool VerifyMultiTargetsScan(ImageScanResultWithCVEs scanResult) { var scanDate = DateTimeOffset.FromUnixTimeSeconds(1583894785).DateTime; scanResult.Id.Should().Be("12240604-22b6-4a8b-b1f7-0faddd20c37e"); scanResult.Date.Should().BeCloseTo(scanDate, TimeSpan.FromMinutes(1)); scanResult.ImageTag.Should().Be("registry.com/repository/multi-target-image:v0.2"); scanResult.Status.Should().Be(ImageScanStatus.Succeeded); scanResult.Description.Should().BeNullOrEmpty(); scanResult.FoundCVEs.Select(i => i.Target).Distinct().Should().HaveCount(2); scanResult.FoundCVEs.Should().HaveCount(12); foreach (var counter in scanResult.Counters) { switch (counter.Severity) { case CveSeverity.Critical: counter.Count.Should().Be(1, "multi target result should have 1 CRITICAL priority CVE"); break; case CveSeverity.High: counter.Count.Should().Be(4, "multi target result should have 4 HIGH priority CVEs"); break; case CveSeverity.Medium: counter.Count.Should().Be(4, "multi target result should have 4 MEDIUM priority CVEs"); break; case CveSeverity.Low: counter.Count.Should().Be(2, "multi target result should have 2 LOW priority CVEs"); break; case CveSeverity.Unknown: counter.Count.Should().Be(1, "multi target result should have 1 CVE with UNKNOWN severity"); break; default: break; } } return(true); }
/// <inheritdoc /> public async Task SaveImageScanResult(ImageScanResultWithCVEs imageScanResult) { Logger.Information( "Saving Image Scan {ImageScanId} for {ImageTag} with {FoundCVE} found CVEs", imageScanResult.Id, imageScanResult.ImageTag, imageScanResult.FoundCVEs?.Count ?? 0); var existingScanResult = await this.db.Set <ImageScanResultEntity>().FirstOrDefaultAsync(i => i.ExternalId == imageScanResult.Id); if (existingScanResult == null) { this.db.Set <ImageScanResultEntity>().Add(imageScanResult.ToEntity()); } else { var newEntity = imageScanResult.ToEntity(); existingScanResult.Date = newEntity.Date; existingScanResult.FoundCVEs = newEntity.FoundCVEs; existingScanResult.Status = newEntity.Status; existingScanResult.Description = newEntity.Description; this.db.Set <ImageScanResultEntity>().Update(existingScanResult); } // update in-progress check-results var checkResults = await this.db.Set <CheckResultEntity>() .Where(i => i.Value == CheckValue.InProgress && i.ComponentId.EndsWith(imageScanResult.ImageTag)) .ToArrayAsync(); foreach (var result in checkResults) { result.Value = imageScanResult.GetCheckResultValue().ToEntity(); result.Message = imageScanResult.GetCheckResultMessage(); this.db.Update(result); } await this.db.SaveChangesAsync(); }
private async Task <ImageScanResultWithCVEs> NormalizeRawData(AuditBlob auditBlob, AuditMetadata auditMetadata) { var auditDate = DateTimeOffset.FromUnixTimeSeconds(auditMetadata.Timestamp).DateTime; var scanResult = new ImageScanResultWithCVEs { Id = auditMetadata.AuditId, Date = auditDate, ImageTag = auditMetadata.ImageTag, }; if (auditMetadata.AuditResult != "succeeded") { var path = $"{auditBlob.ParentContainer.Name}/{auditBlob.Name}"; Logger.Warning( "Audit {AuditPath} result is {AuditResult} due: {FailureReason}", path, auditMetadata.AuditResult, auditMetadata.FailureDescription); scanResult.Status = ImageScanStatus.Failed; scanResult.Description = TrivyScanDescriptionNormalizer.ToHumanReadable(auditMetadata.FailureDescription); } else { var auditResultFilePath = $"{auditBlob.ParentContainer.Name}/{auditMetadata.TrivyAuditPath}"; var(entities, counters) = await this.ParseScanTargets(auditResultFilePath); scanResult.FoundCVEs = entities; scanResult.Counters = counters; scanResult.Status = ImageScanStatus.Succeeded; Logger.Information( "Successfully processed {ImageTag} image scan of {AuditDate} with {ScanSummary}", scanResult.ImageTag, scanResult.Date, scanResult.GetCheckResultMessage()); } return(scanResult); }
private static bool VerifySingleTargetScan(ImageScanResultWithCVEs scanResult) { // NOTE: the result has EIGHT CVE, but CVE-2019-13050 and CVE-2019-14855 are found in two different packages // Thus, there are EIGHT FoundCVEs, but counters has only SIX items (two medium and two low instead of three each) var scanDate = DateTimeOffset.FromUnixTimeSeconds(1583894784).DateTime; scanResult.Id.Should().Be("33340604-22b6-4a8b-b1f7-0faddd20c37a"); scanResult.Date.Should().BeCloseTo(scanDate, TimeSpan.FromMinutes(1)); scanResult.ImageTag.Should().Be("registry.com/repository/single-target-image:v0.2"); scanResult.Status.Should().Be(ImageScanStatus.Succeeded); scanResult.Description.Should().BeNullOrEmpty(); scanResult.FoundCVEs.Select(i => i.Target).Distinct().Should().HaveCount(1); scanResult.FoundCVEs.Should().HaveCount(8); foreach (var counter in scanResult.Counters) { switch (counter.Severity) { case CveSeverity.High: counter.Count.Should().Be(2, "single target result should have 2 HIGH priority CVEs"); break; case CveSeverity.Medium: counter.Count.Should().Be(2, "single target result should have 3 MEDIUM priority CVEs"); break; case CveSeverity.Low: counter.Count.Should().Be(2, "single target result should have 3 LOW priority CVEs"); break; default: break; } } return(true); }
public async Task SaveImageScanResultUpdatesInProgressCheckResults() { // Arrange await using var context = JosekiTestsDb.CreateUniqueContext(); var scan = new ImageScanResultWithCVEs { Date = DateTime.UtcNow, Id = Guid.NewGuid().ToString(), Description = Guid.NewGuid().ToString(), ImageTag = Guid.NewGuid().ToString(), Status = ImageScanStatus.Succeeded, FoundCVEs = new List <ImageScanToCve> { new ImageScanToCve { InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, }, Counters = new[] { new VulnerabilityCounter { Count = 1, Severity = CveSeverity.High }, new VulnerabilityCounter { Count = 2, Severity = CveSeverity.Medium }, }, }; // only the first one should be updated var checkResults = new[] { new CheckResultEntity { Id = 100, Value = joseki.db.entities.CheckValue.InProgress, ComponentId = $"/k8s/.../image/{scan.ImageTag}", DateUpdated = DateTime.UtcNow.AddDays(-1) }, new CheckResultEntity { Id = 200, Value = joseki.db.entities.CheckValue.InProgress, ComponentId = $"/k8s/.../image/{Guid.NewGuid()}", DateUpdated = DateTime.UtcNow.AddDays(-1) }, new CheckResultEntity { Id = 300, Value = joseki.db.entities.CheckValue.Succeeded, ComponentId = $"/k8s/.../image/{scan.ImageTag}", DateUpdated = DateTime.UtcNow.AddDays(-1) }, }; // this is the hack -_- // Use sync version, because it does not update DateUpdated & DateCreated context.CheckResult.AddRange(checkResults); context.SaveChanges(); var parser = new ConfigurationParser("config.sample.yaml"); var db = new MssqlJosekiDatabase(context, parser); // Act & Assert var saveRequestedAt = DateTime.UtcNow; await db.SaveImageScanResult(scan); var updatedCheckResults = await context.CheckResult.Where(i => i.DateUpdated >= saveRequestedAt).ToArrayAsync(); updatedCheckResults.Should().HaveCount(1); updatedCheckResults.First().Id.Should().Be(100); }
public async Task SaveImageScanResultCouldUpdateExistingEntity() { // Arrange await using var context = JosekiTestsDb.CreateUniqueContext(); var queuedScan = new ImageScanResultEntity { Date = DateTime.UtcNow.AddMinutes(-10), ExternalId = Guid.NewGuid().ToString(), Description = Guid.NewGuid().ToString(), ImageTag = Guid.NewGuid().ToString(), Status = joseki.db.entities.ImageScanStatus.Queued, }; context.Set <ImageScanResultEntity>().Add(queuedScan); await context.SaveChangesAsync(); var scan = new ImageScanResultWithCVEs { Date = DateTime.UtcNow, Id = queuedScan.ExternalId, Description = Guid.NewGuid().ToString(), ImageTag = queuedScan.ImageTag, Status = ImageScanStatus.Succeeded, FoundCVEs = new List <ImageScanToCve> { new ImageScanToCve { InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, new ImageScanToCve { InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString() }, }, }; var parser = new ConfigurationParser("config.sample.yaml"); var db = new MssqlJosekiDatabase(context, parser); // Act & Assert context.ImageScanResult.Should().HaveCount(1); context.ImageScanResultToCve.Should().HaveCount(0); await db.SaveImageScanResult(scan); context.ImageScanResult.Should().HaveCount(1); context.ImageScanResultToCve.Should().HaveCount(scan.FoundCVEs.Count); // Only Date, Description, Status, and CVEs should be updated var actual = await context.ImageScanResult.FirstAsync(); actual.Date.Should().Be(scan.Date); actual.Description.Should().Be(scan.Description); actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Succeeded); foreach (var actualCve in await context.ImageScanResultToCve.ToArrayAsync()) { var expectedCve = scan.FoundCVEs.First(i => i.InternalCveId == actualCve.CveId); actualCve.Target.Should().Be(expectedCve.Target); actualCve.UsedPackage.Should().Be(expectedCve.UsedPackage); actualCve.UsedPackageVersion.Should().Be(expectedCve.UsedPackageVersion); } }