Skip to content

Commit 23b635c

Browse files
authored
Merge pull request #730 from snyk/oci-pull-image-support
add support for pull oci images
2 parents 19b6341 + 7384733 commit 23b635c

File tree

7 files changed

+64
-25
lines changed

7 files changed

+64
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ __pycache__
5959
opm
6060
.operator_version
6161
snyk-monitor-catalog-source.yaml
62+
*.iml

src/scanner/images/index.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DepGraph, legacy } from '@snyk/dep-graph';
44

55
import { logger } from '../../common/logger';
66
import { pull as skopeoCopy, getDestinationForImage } from './skopeo';
7-
import { IPullableImage, IScanImage } from './types';
7+
import {IPullableImage, IScanImage, SkopeoRepositoryType} from './types';
88
import { IScanResult } from '../types';
99
import {
1010
buildDockerPropertiesOnDepTree,
@@ -13,30 +13,43 @@ import {
1313
LegacyPluginResponse,
1414
} from './docker-plugin-shim';
1515

16-
export async function pullImages(images: IPullableImage[]): Promise<IPullableImage[]> {
17-
const pulledImages: IPullableImage[] = [];
18-
19-
for (const image of images) {
20-
const { imageName, imageWithDigest, fileSystemPath } = image;
21-
if (!fileSystemPath) {
22-
continue;
23-
}
24-
16+
/*
17+
pulled images by skopeo archive repo type:
18+
1st try to pull by docker archive image if it fail try to pull by oci archive
19+
*/
20+
async function pullImageBySkopeoRepo(imageToPull: IPullableImage): Promise<IPullableImage> {
21+
// Scan image by digest if exists, other way fallback tag
22+
const scanId = imageToPull.imageWithDigest ?? imageToPull.imageName;
23+
imageToPull.skopeoRepoType = SkopeoRepositoryType.DockerArchive;
2524
try {
26-
// Scan image by digest if exists, other way fallback tag
27-
const scanId = imageWithDigest ?? imageName;
28-
await skopeoCopy(scanId, fileSystemPath);
29-
pulledImages.push(image);
30-
} catch (error) {
31-
logger.error({error, image: imageWithDigest}, 'failed to pull image');
25+
// copy docker archive image
26+
await skopeoCopy(scanId, imageToPull.fileSystemPath, imageToPull.skopeoRepoType);
27+
} catch (dockerError) {
28+
imageToPull.skopeoRepoType = SkopeoRepositoryType.OciArchive;
29+
// copy oci archive image
30+
await skopeoCopy(scanId, imageToPull.fileSystemPath, imageToPull.skopeoRepoType);
3231
}
33-
}
32+
return imageToPull;
33+
}
3434

35-
return pulledImages;
35+
export async function pullImages(images: IPullableImage[]): Promise<IPullableImage[]> {
36+
const pulledImages: IPullableImage[] = [];
37+
for (const image of images) {
38+
if (!image.fileSystemPath) {
39+
continue;
40+
}
41+
try {
42+
const pulledImage = await pullImageBySkopeoRepo(image);
43+
pulledImages.push(pulledImage);
44+
} catch (error) {
45+
logger.error({error, image: image.imageWithDigest?? image.imageName}, 'failed to pull image docker/oci archive image');
46+
}
47+
}
48+
return pulledImages;
3649
}
3750

38-
export function getImagesWithFileSystemPath(images: IScanImage[]): IPullableImage[] {
39-
return images.map((image) => ({ ...image, fileSystemPath: getDestinationForImage(image.imageName) }));
51+
export function getImagesWithFileSystemPath(images: IScanImage[]): { imageName: string; skopeoRepoType: SkopeoRepositoryType; fileSystemPath: string; imageWithDigest?: string }[] {
52+
return images.map((image) => ({...image, fileSystemPath: getDestinationForImage(image.imageName)}));
4053
}
4154

4255
export async function removePulledImages(images: IPullableImage[]): Promise<void> {
@@ -75,10 +88,11 @@ export function getImageParts(imageWithTag: string) : {imageName: string, imageT
7588
export async function scanImages(images: IPullableImage[]): Promise<IScanResult[]> {
7689
const scannedImages: IScanResult[] = [];
7790

78-
for (const { imageName, fileSystemPath, imageWithDigest } of images) {
91+
for (const { imageName, fileSystemPath, imageWithDigest, skopeoRepoType } of images) {
7992
try {
8093
const shouldIncludeAppVulns = true;
81-
const dockerArchivePath = `docker-archive:${fileSystemPath}`;
94+
const archiveType= skopeoRepoType == SkopeoRepositoryType.DockerArchive?"docker-archive":"oci-archive";
95+
const dockerArchivePath = `${archiveType}:${fileSystemPath}`;
8296

8397
const pluginResponse = await scan({
8498
path: dockerArchivePath,

src/scanner/images/skopeo.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function prefixRespository(target: string, type: SkopeoRepositoryType): string {
2626
case SkopeoRepositoryType.ImageRegistry:
2727
return `${type}://${target}`;
2828
case SkopeoRepositoryType.DockerArchive:
29+
case SkopeoRepositoryType.OciArchive:
2930
return `${type}:${target}`;
3031
default:
3132
throw new Error(`Unhandled Skopeo repository type ${type}`);
@@ -35,6 +36,7 @@ function prefixRespository(target: string, type: SkopeoRepositoryType): string {
3536
export async function pull(
3637
image: string,
3738
destination: string,
39+
skopeoRepoType: SkopeoRepositoryType,
3840
): Promise<void> {
3941
const creds = await credentials.getSourceCredentials(image);
4042
const credentialsParameters = getCredentialParameters(creds);
@@ -45,7 +47,7 @@ export async function pull(
4547
args.push(...credentialsParameters);
4648
args.push(...certificatesParameters);
4749
args.push({body: prefixRespository(image, SkopeoRepositoryType.ImageRegistry), sanitise: false});
48-
args.push({body: prefixRespository(destination, SkopeoRepositoryType.DockerArchive), sanitise: false});
50+
args.push({body: prefixRespository(destination, skopeoRepoType), sanitise: false});
4951

5052
await pullWithRetry(args, destination);
5153
}

src/scanner/images/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
export interface IScanImage {
22
imageName: string;
33
imageWithDigest?: string;
4+
skopeoRepoType: SkopeoRepositoryType;
45
}
56

67
export interface IPullableImage {
78
imageName: string;
89
imageWithDigest?: string;
910
fileSystemPath: string;
11+
skopeoRepoType: SkopeoRepositoryType;
1012
}
1113

1214
/**

test/fixtures/oci-dummy-pod.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: oci-dummy
5+
namespace: services
6+
labels:
7+
app: oci-dummy
8+
spec:
9+
imagePullSecrets:
10+
- name: docker-io
11+
containers:
12+
- name: oci-dummy
13+
image: snyk/runtime-fixtures:oci-dummy
14+
command: ['/app/sample']

test/integration/kubernetes.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ test('deploy sample workloads', async () => {
4141
'alpine@sha256:7746df395af22f04212cd25a92c1d6dbc5a06a0ca9579a229ef43008d4d1302a';
4242
await Promise.all([
4343
kubectl.applyK8sYaml('./test/fixtures/alpine-pod.yaml'),
44+
kubectl.applyK8sYaml('./test/fixtures/oci-dummy-pod.yaml'),
4445
kubectl.applyK8sYaml('./test/fixtures/nginx-replicationcontroller.yaml'),
4546
kubectl.applyK8sYaml('./test/fixtures/redis-deployment.yaml'),
4647
kubectl.applyK8sYaml('./test/fixtures/centos-deployment.yaml'),
@@ -100,10 +101,13 @@ test('snyk-monitor sends data to kubernetes-upstream', async () => {
100101
const validatorFn: WorkloadLocatorValidator = (workloads) => {
101102
return (
102103
workloads !== undefined &&
103-
workloads.length === 7 &&
104+
workloads.length === 8 &&
104105
workloads.find(
105106
(workload) => workload.name === 'alpine' && workload.type === WorkloadKind.Pod,
106107
) !== undefined &&
108+
workloads.find(
109+
(workload) => workload.name === 'oci-dummy' && workload.type === WorkloadKind.Pod,
110+
) !== undefined &&
107111
workloads.find(
108112
(workload) =>
109113
workload.name === 'nginx' && workload.type === WorkloadKind.ReplicationController,

test/unit/scanner/images.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as tap from 'tap';
22

3-
import {IPullableImage, IScanImage} from '../../../src/scanner/images/types';
3+
import {IPullableImage, IScanImage, SkopeoRepositoryType} from '../../../src/scanner/images/types';
44
import { config } from '../../../src/common/config';
55
import * as scannerImages from '../../../src/scanner/images';
66

@@ -12,6 +12,7 @@ tap.test('getImagesWithFileSystemPath()', async (t) => {
1212
const image: IScanImage[] = [{
1313
imageName: 'nginx:latest',
1414
imageWithDigest: 'nginx@sha256:4949aa7259aa6f827450207db5ad94cabaa9248277c6d736d5e1975d200c7e43',
15+
skopeoRepoType: SkopeoRepositoryType.DockerArchive
1516
}];
1617
const imageResult = scannerImages.getImagesWithFileSystemPath(image);
1718
t.same(imageResult.length, 1, 'expected 1 item');
@@ -33,6 +34,7 @@ tap.test('getImagesWithFileSystemPath()', async (t) => {
3334
const someImage = [{
3435
imageName: 'centos:latest',
3536
imageWithDigest: 'centos@sha256:fc4a234b91cc4b542bac8a6ad23b2ddcee60ae68fc4dbd4a52efb5f1b0baad71',
37+
skopeoRepoType: SkopeoRepositoryType.DockerArchive
3638
}];
3739
const firstCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];
3840
const secondCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];

0 commit comments

Comments
 (0)