/// <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);
        }
Example #3
0
        /// <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);
        }
Example #7
0
        /// <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();
        }
Example #10
0
        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);
            }
        }