From 97c1fdcd8e1e6d29e14bb13c45438c04da42f9f1 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:34:40 +0800 Subject: [PATCH] Add Referrers API testcase (#18775) Fix #18617 Signed-off-by: Yang Jiao --- tests/apitests/python/library/cosign.py | 11 ++ .../apitests/python/library/referrers_api.py | 12 ++ tests/apitests/python/test_referrers_api.py | 117 ++++++++++++++++++ tests/files/sbom_test.json | 46 +++++++ tests/robot-cases/Group0-BAT/API_DB.robot | 4 + 5 files changed, 190 insertions(+) create mode 100644 tests/apitests/python/library/referrers_api.py create mode 100644 tests/apitests/python/test_referrers_api.py create mode 100644 tests/files/sbom_test.json diff --git a/tests/apitests/python/library/cosign.py b/tests/apitests/python/library/cosign.py index df3c4cdf4..7638b1903 100644 --- a/tests/apitests/python/library/cosign.py +++ b/tests/apitests/python/library/cosign.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- import base +import os def generate_key_pair(): + config_key_file = "cosign.key" + config_pub_file = "cosign.pub" + if os.path.exists(config_key_file) and os.path.exists(config_pub_file): + os.remove(config_key_file) + os.remove(config_pub_file) command = ["cosign", "generate-key-pair"] base.run_command(command) def sign_artifact(artifact): command = ["cosign", "sign", "-y", "--allow-insecure-registry", "--key", "cosign.key", artifact] base.run_command(command) + +def push_artifact_sbom(artifact, sbom_path, type="spdx"): + command = ["cosign", "attach", "sbom", "--allow-insecure-registry", "--registry-referrers-mode", "oci-1-1", + "--type", type, "--sbom", sbom_path, artifact] + base.run_command(command) diff --git a/tests/apitests/python/library/referrers_api.py b/tests/apitests/python/library/referrers_api.py new file mode 100644 index 000000000..5d51a6d53 --- /dev/null +++ b/tests/apitests/python/library/referrers_api.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +import requests + +def call(server, project_name, repo_name, digest, artifactType=None, **kwargs): + url=None + auth = (kwargs.get("username"), kwargs.get("password")) + if artifactType: + artifactType = artifactType.replace("+", "%2B") + url="https://{}/v2/{}/{}/referrers/{}?artifactType={}".format(server, project_name, repo_name, digest, artifactType) + else: + url="https://{}/v2/{}/{}/referrers/{}".format(server, project_name, repo_name, digest) + return requests.get(url, auth=auth, verify=False) diff --git a/tests/apitests/python/test_referrers_api.py b/tests/apitests/python/test_referrers_api.py new file mode 100644 index 000000000..f1bd69f92 --- /dev/null +++ b/tests/apitests/python/test_referrers_api.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +import unittest + +from testutils import harbor_server, files_directory, ADMIN_CLIENT, suppress_urllib3_warning +from library import cosign, referrers_api +from library.project import Project +from library.user import User +from library.artifact import Artifact +from library.repository import push_self_build_image_to_project +from library import docker_api + +class TestReferrersApi(unittest.TestCase): + + @suppress_urllib3_warning + def setUp(self): + self.project= Project() + self.user= User() + self.artifact = Artifact() + self.image = "artifact_test" + self.tag = "dev" + self.sbom_path = files_directory + "sbom_test.json" + self.sbom_artifact_type = "application/vnd.dev.cosign.artifact.sbom.v1+json" + self.signature_artifact_type = "application/vnd.oci.image.config.v1+json" + + def testReferrersApi(self): + """ + Test case: + Referrers Api + Test step and expected result: + 1. Create a new user(UA); + 2. Create a new project(PA) by user(UA); + 3. Push a new image(IA) in project(PA) by user(UA); + 4. Push image(IA) SBOM to project(PA) by user(UA); + 5. Sign image(IA) with cosign; + 6. Sign image(IA) SBOM with cosign; + 7. Call the referrers api successfully; + 8. Call the referrers api and filter artifact_type; + Tear down: + 1. Delete project(PA); + 2. Delete user(UA). + """ + url = ADMIN_CLIENT["endpoint"] + user_password = "Aa123456" + + # 1. Create user(UA) + _, user_name = self.user.create_user(user_password = user_password, **ADMIN_CLIENT) + user_client = dict(endpoint = url, username = user_name, password = user_password, with_accessory = True) + + # 2. Create private project(PA) by user(UA) + _, project_name = self.project.create_project(metadata = {"public": "false"}, **user_client) + + # 3. Push a new image(IA) in project(PA) by user(UA) + push_self_build_image_to_project(project_name, harbor_server, user_name, user_password, self.image, self.tag) + + # 4. Push image(IA) SBOM to project(PA) by user(UA) + docker_api.docker_login_cmd(harbor_server, user_name, user_password, enable_manifest = False) + cosign.push_artifact_sbom("{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag), self.sbom_path) + artifact_info = self.artifact.get_reference_info(project_name, self.image, self.tag, **user_client) + artifact_digest = artifact_info.digest + sbom_digest = artifact_info.accessories[0].digest + + # 5. Sign image(IA) with cosign + cosign.generate_key_pair() + cosign.sign_artifact("{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag)) + artifact_info = self.artifact.get_reference_info(project_name, self.image, self.tag, **user_client) + self.assertEqual(len(artifact_info.accessories), 2) + signature_digest = None + for accessory in artifact_info.accessories: + if accessory.digest != sbom_digest: + signature_digest = accessory.digest + break + + # 6. Sign image(IA) SBOM cosign + cosign.sign_artifact("{}/{}/{}@{}".format(harbor_server, project_name, self.image, sbom_digest)) + + # 7. Call the referrers api successfully + res_json = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, **user_client).json() + self.assertEqual(len(res_json["manifests"]), 2) + for manifest in res_json["manifests"]: + self.assertIn(manifest["digest"], [signature_digest, sbom_digest]) + self.assertIn(manifest["artifactType"], [self.signature_artifact_type, self.sbom_artifact_type]) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + res_json = referrers_api.call(harbor_server, project_name, self.image, sbom_digest, **user_client).json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertIsNotNone(manifest["digest"]) + self.assertIsNotNone(manifest["artifactType"], [self.signature_artifact_type, self.sbom_artifact_type]) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + # 8. Call the referrers api and filter artifact_type + res = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, self.sbom_artifact_type, **user_client) + self.assertEqual(res.headers["Oci-Filters-Applied"], "artifactType") + res_json = res.json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertEqual(manifest["digest"], sbom_digest) + self.assertIn(manifest["artifactType"], self.sbom_artifact_type) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + res = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, self.signature_artifact_type, **user_client) + self.assertEqual(res.headers["Oci-Filters-Applied"], "artifactType") + res_json = res.json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertEqual(manifest["digest"], signature_digest) + self.assertIn(manifest["artifactType"], self.signature_artifact_type) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/files/sbom_test.json b/tests/files/sbom_test.json new file mode 100644 index 000000000..aa26ace07 --- /dev/null +++ b/tests/files/sbom_test.json @@ -0,0 +1,46 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2023-05-31T07:43:35.672590648Z", + "creators": [ + "Tool: trivy", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-ContainerImage-8e8b2798af13ee89" + ], + "documentNamespace": "http://aquasecurity.github.io/trivy/container_image/artifact_test:dev-9648faf4-428e-46ad-a9e6-1d3f84c6f297", + "name": "artifact_test:dev", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-8e8b2798af13ee89", + "attributionTexts": [ + "SchemaVersion: 2", + "ImageID: sha256:9517d37fc3457e070719ed8dead2a9134dd9d5126dd6ef55d15685337c3dc711", + "RepoDigest: 10.202.250.222/test02/test@sha256:0feefb1b81993c1299a7f75a2c86d7cfed4b25859037657377d563d955e8e20f", + "DiffID: sha256:6b245f040973e14e29f371ff2b4059c84ab2de8b7b9a04bc21fd4a7b0a72c446", + "DiffID: sha256:6491a698ac806b35795f23098e169f50b2b6f179900b5625e5677cb1df2651ca", + "RepoTag: artifact_test:dev" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:oci/test@sha256:0feefb1b81993c1299a7f75a2c86d7cfed4b25859037657377d563d955e8e20f?repository_url=10.202.250.222%2Ftest02%2Ftest\u0026arch=amd64", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "name": "artifact_test:dev" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-8e8b2798af13ee89", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + } + ], + "spdxVersion": "SPDX-2.2" +} \ No newline at end of file diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index 022e3f2aa..ed9da3957 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -182,3 +182,7 @@ Test Case - Job Service Dashboard Test Case - Retain Image Last Pull Time [Tags] retain_image_last_pull_time Harbor API Test ./tests/apitests/python/test_retain_image_last_pull_time.py + +Test Case - Referrers API + [Tags] referrers + Harbor API Test ./tests/apitests/python/test_referrers_api.py