Skip to content

Commit d1f6818

Browse files
committed
Add image scope scanning option to the Linux detector
1 parent 601ebcd commit d1f6818

File tree

6 files changed

+120
-9
lines changed

6 files changed

+120
-9
lines changed

src/Microsoft.ComponentDetection.Detectors/linux/ILinuxScanner.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ public interface ILinuxScanner
1919
/// <param name="containerLayers">The collection of Docker layers that make up the container image.</param>
2020
/// <param name="baseImageLayerCount">The number of layers that belong to the base image, used to distinguish base image layers from application layers.</param>
2121
/// <param name="enabledComponentTypes">The set of component types to include in the scan results. Only components matching these types will be returned.</param>
22+
/// <param name="scope">The scope for scanning the image. See <see cref="LinuxScannerScope"/> for values.</param>
2223
/// <param name="cancellationToken">A token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
2324
/// <returns>A task that represents the asynchronous operation. The task result contains a collection of <see cref="LayerMappedLinuxComponents"/> representing the components found in the image and their associated layers.</returns>
2425
public Task<IEnumerable<LayerMappedLinuxComponents>> ScanLinuxAsync(
2526
string imageHash,
2627
IEnumerable<DockerLayer> containerLayers,
2728
int baseImageLayerCount,
2829
ISet<ComponentType> enabledComponentTypes,
30+
LinuxScannerScope scope,
2931
CancellationToken cancellationToken = default
3032
);
3133
}

src/Microsoft.ComponentDetection.Detectors/linux/LinuxContainerDetector.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ ILogger<LinuxContainerDetector> logger
2828
{
2929
private const string TimeoutConfigKey = "Linux.ScanningTimeoutSec";
3030
private const int DefaultTimeoutMinutes = 10;
31+
private const string ScanScopeConfigKey = "Linux.ImageScanScope";
32+
private const LinuxScannerScope DefaultScanScope = LinuxScannerScope.AllLayers;
3133

3234
private readonly ILinuxScanner linuxScanner = linuxScanner;
3335
private readonly IDockerService dockerService = dockerService;
@@ -77,6 +79,8 @@ public async Task<IndividualDetectorScanResult> ExecuteDetectorAsync(
7779
return EmptySuccessfulScan();
7880
}
7981

82+
var scannerScope = GetScanScope(request.DetectorArgs);
83+
8084
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
8185
timeoutCts.CancelAfter(GetTimeout(request.DetectorArgs));
8286

@@ -96,6 +100,7 @@ public async Task<IndividualDetectorScanResult> ExecuteDetectorAsync(
96100
results = await this.ProcessImagesAsync(
97101
imagesToProcess,
98102
request.ComponentRecorder,
103+
scannerScope,
99104
timeoutCts.Token
100105
);
101106
}
@@ -137,6 +142,26 @@ private static TimeSpan GetTimeout(IDictionary<string, string> detectorArgs)
137142
: defaultTimeout;
138143
}
139144

145+
/// <summary>
146+
/// Extracts and returns the scan scope from detector arguments.
147+
/// </summary>
148+
/// <param name="detectorArgs">The arguments provided by the user.</param>
149+
/// <returns>The <see cref="LinuxScannerScope"/> to use for scanning. Defaults to <see cref="DefaultScanScope"/> if not specified.</returns>
150+
private static LinuxScannerScope GetScanScope(IDictionary<string, string> detectorArgs)
151+
{
152+
if (detectorArgs == null || !detectorArgs.TryGetValue(ScanScopeConfigKey, out var scopeValue))
153+
{
154+
return DefaultScanScope;
155+
}
156+
157+
return scopeValue?.ToUpperInvariant() switch
158+
{
159+
"ALL-LAYERS" => LinuxScannerScope.AllLayers,
160+
"SQUASHED" => LinuxScannerScope.Squashed,
161+
_ => DefaultScanScope,
162+
};
163+
}
164+
140165
private static IndividualDetectorScanResult EmptySuccessfulScan() =>
141166
new() { ResultCode = ProcessingResultCode.Success };
142167

@@ -179,6 +204,7 @@ private static void RecordImageDetectionFailure(Exception exception, string imag
179204
private async Task<IEnumerable<ImageScanningResult>> ProcessImagesAsync(
180205
IEnumerable<string> imagesToProcess,
181206
IComponentRecorder componentRecorder,
207+
LinuxScannerScope scannerScope,
182208
CancellationToken cancellationToken = default
183209
)
184210
{
@@ -249,6 +275,7 @@ await this.dockerService.InspectImageAsync(image, cancellationToken)
249275
internalContainerDetails.Layers,
250276
baseImageLayerCount,
251277
enabledComponentTypes,
278+
scannerScope,
252279
cancellationToken
253280
);
254281

src/Microsoft.ComponentDetection.Detectors/linux/LinuxScanner.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ public class LinuxScanner : ILinuxScanner
2727
private static readonly IList<string> CmdParameters =
2828
[
2929
"--quiet",
30-
"--scope",
31-
"all-layers",
3230
"--output",
3331
"json",
3432
];
3533

34+
private static readonly IList<string> ScopeAllLayersParameter = ["--scope", "all-layers"];
35+
36+
private static readonly IList<string> ScopeSquashedParameter = ["--scope", "squashed"];
37+
3638
private static readonly SemaphoreSlim ContainerSemaphore = new SemaphoreSlim(2);
3739

3840
private static readonly int SemaphoreTimeout = Convert.ToInt32(
@@ -96,6 +98,7 @@ public async Task<IEnumerable<LayerMappedLinuxComponents>> ScanLinuxAsync(
9698
IEnumerable<DockerLayer> containerLayers,
9799
int baseImageLayerCount,
98100
ISet<ComponentType> enabledComponentTypes,
101+
LinuxScannerScope scope,
99102
CancellationToken cancellationToken = default
100103
)
101104
{
@@ -120,6 +123,17 @@ public async Task<IEnumerable<LayerMappedLinuxComponents>> ScanLinuxAsync(
120123
{
121124
var command = new List<string> { imageHash }
122125
.Concat(CmdParameters)
126+
.Concat(
127+
scope switch
128+
{
129+
LinuxScannerScope.AllLayers => ScopeAllLayersParameter,
130+
LinuxScannerScope.Squashed => ScopeSquashedParameter,
131+
_ => throw new ArgumentOutOfRangeException(
132+
nameof(scope),
133+
$"Unsupported scope value: {scope}"
134+
),
135+
}
136+
)
123137
.ToList();
124138
(stdout, stderr) = await this.dockerService.CreateAndRunContainerAsync(
125139
ScannerImage,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Microsoft.ComponentDetection.Detectors.Linux;
2+
3+
/// <summary>
4+
/// Defines the scope for scanning Linux container images.
5+
/// </summary>
6+
public enum LinuxScannerScope
7+
{
8+
/// <summary>
9+
/// Scan files from all layers of the image.
10+
/// </summary>
11+
AllLayers,
12+
13+
/// <summary>
14+
/// Scan only the files accessible from the final layer of the image.
15+
/// </summary>
16+
Squashed,
17+
}

test/Microsoft.ComponentDetection.Detectors.Tests/LinuxContainerDetectorTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public LinuxContainerDetectorTests()
7373
It.IsAny<IEnumerable<DockerLayer>>(),
7474
It.IsAny<int>(),
7575
It.IsAny<ISet<ComponentType>>(),
76+
It.IsAny<LinuxScannerScope>(),
7677
It.IsAny<CancellationToken>()
7778
)
7879
)
@@ -277,6 +278,7 @@ public async Task TestLinuxContainerDetector_SameImagePassedMultipleTimesAsync()
277278
It.IsAny<IEnumerable<DockerLayer>>(),
278279
It.IsAny<int>(),
279280
It.IsAny<ISet<ComponentType>>(),
281+
It.IsAny<LinuxScannerScope>(),
280282
It.IsAny<CancellationToken>()
281283
),
282284
Times.Once
@@ -307,6 +309,48 @@ public async Task TestLinuxContainerDetector_TimeoutParameterSpecifiedAsync()
307309
await action.Should().NotThrowAsync<OperationCanceledException>();
308310
}
309311

312+
[TestMethod]
313+
[DataRow("all-layers", LinuxScannerScope.AllLayers)]
314+
[DataRow("squashed", LinuxScannerScope.Squashed)]
315+
[DataRow("ALL-LAYERS", LinuxScannerScope.AllLayers)]
316+
[DataRow("SQUASHED", LinuxScannerScope.Squashed)]
317+
[DataRow(null, LinuxScannerScope.AllLayers)] // Test default behavior
318+
[DataRow("", LinuxScannerScope.AllLayers)] // Test empty string default
319+
[DataRow("invalid-value", LinuxScannerScope.AllLayers)] // Test invalid input defaults to AllLayers
320+
public async Task TestLinuxContainerDetector_ImageScanScopeParameterSpecifiedAsync(string scopeValue, LinuxScannerScope expectedScope)
321+
{
322+
var detectorArgs = new Dictionary<string, string> { { "Linux.ImageScanScope", scopeValue } };
323+
var scanRequest = new ScanRequest(
324+
new DirectoryInfo(Path.GetTempPath()),
325+
(_, __) => false,
326+
this.mockLogger.Object,
327+
detectorArgs,
328+
[NodeLatestImage],
329+
new ComponentRecorder()
330+
);
331+
332+
var linuxContainerDetector = new LinuxContainerDetector(
333+
this.mockSyftLinuxScanner.Object,
334+
this.mockDockerService.Object,
335+
this.mockLinuxContainerDetectorLogger.Object
336+
);
337+
338+
await linuxContainerDetector.ExecuteDetectorAsync(scanRequest);
339+
340+
this.mockSyftLinuxScanner.Verify(
341+
scanner =>
342+
scanner.ScanLinuxAsync(
343+
It.IsAny<string>(),
344+
It.IsAny<IEnumerable<DockerLayer>>(),
345+
It.IsAny<int>(),
346+
It.IsAny<ISet<ComponentType>>(),
347+
expectedScope,
348+
It.IsAny<CancellationToken>()
349+
),
350+
Times.Once
351+
);
352+
}
353+
310354
[TestMethod]
311355
public async Task TestLinuxContainerDetector_HandlesScratchBaseAsync()
312356
{

test/Microsoft.ComponentDetection.Detectors.Tests/LinuxScannerTests.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ await this.linuxScanner.ScanLinuxAsync(
288288
},
289289
],
290290
0,
291-
enabledTypes
291+
enabledTypes,
292+
LinuxScannerScope.AllLayers
292293
)
293294
)
294295
.First()
@@ -335,7 +336,8 @@ await this.linuxScanner.ScanLinuxAsync(
335336
},
336337
],
337338
0,
338-
enabledTypes
339+
enabledTypes,
340+
LinuxScannerScope.AllLayers
339341
)
340342
)
341343
.First()
@@ -384,7 +386,8 @@ await this.linuxScanner.ScanLinuxAsync(
384386
},
385387
],
386388
0,
387-
enabledTypes
389+
enabledTypes,
390+
LinuxScannerScope.AllLayers
388391
)
389392
)
390393
.First()
@@ -433,7 +436,8 @@ await this.linuxScanner.ScanLinuxAsync(
433436
},
434437
],
435438
0,
436-
enabledTypes
439+
enabledTypes,
440+
LinuxScannerScope.AllLayers
437441
)
438442
)
439443
.First()
@@ -522,7 +526,8 @@ public async Task TestLinuxScanner_SupportsMultipleComponentTypes_Async()
522526
new DockerLayer { LayerIndex = 1, DiffId = "sha256:layer2" },
523527
],
524528
0,
525-
enabledTypes
529+
enabledTypes,
530+
LinuxScannerScope.AllLayers
526531
);
527532

528533
var allComponents = layers.SelectMany(l => l.Components).ToList();
@@ -622,7 +627,8 @@ public async Task TestLinuxScanner_FiltersComponentsByEnabledTypes_OnlyLinux_Asy
622627
new DockerLayer { LayerIndex = 1, DiffId = "sha256:layer2" },
623628
],
624629
0,
625-
enabledTypes
630+
enabledTypes,
631+
LinuxScannerScope.AllLayers
626632
);
627633

628634
var allComponents = layers.SelectMany(l => l.Components).ToList();
@@ -707,7 +713,8 @@ public async Task TestLinuxScanner_FiltersComponentsByEnabledTypes_OnlyNpmAndPip
707713
new DockerLayer { LayerIndex = 1, DiffId = "sha256:layer2" },
708714
],
709715
0,
710-
enabledTypes
716+
enabledTypes,
717+
LinuxScannerScope.AllLayers
711718
);
712719

713720
var allComponents = layers.SelectMany(l => l.Components).ToList();

0 commit comments

Comments
 (0)