Skip to content

Commit

Permalink
add registry scanning system test
Browse files Browse the repository at this point in the history
Signed-off-by: refaelm <[email protected]>
  • Loading branch information
refaelm92 committed Dec 4, 2024
1 parent 5aeb677 commit cf31851
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 0 deletions.
4 changes: 4 additions & 0 deletions configurations/registry/check_aws.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"registryURI": "015253967648.dkr.ecr.eu-central-1.amazonaws.com",
"accessKeyID": "AKIAQHDJVCMQAN4N4I55"
}
4 changes: 4 additions & 0 deletions configurations/registry/check_azure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"loginServer": "refaelregistrytest.azurecr.io",
"username": "systemtests"
}
3 changes: 3 additions & 0 deletions configurations/registry/check_google.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"registryURI": "europe-docker.pkg.dev"
}
4 changes: 4 additions & 0 deletions configurations/registry/check_quay.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"containerRegistryName": "quay.io",
"robotAccountName": "systemtests+systemtests"
}
6 changes: 6 additions & 0 deletions configurations/registry/create_quay.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"containerRegistryName": "quay.io",
"robotAccountName": "systemtests+systemtests",
"scanFrequency": "*/10 5 1 * *",
"repositories": ["systemtests/webgoat"]
}
4 changes: 4 additions & 0 deletions configurations/system/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .tests_cases import KubescapeTests, KSMicroserviceTests, RuntimeTests
from .tests_cases.ks_vuln_scan_tests import KsVulnerabilityScanningTests
from .tests_cases.notifications_tests import NotificationSTests
from .tests_cases.registry_tests import RegistryTests
from .tests_cases.payments_tests import PaymentTests
from .tests_cases.relevant_vuln_scanning_tests import RelevantVulnerabilityScanningTests
from .tests_cases.seccomp_tests import SeccompProfileTests
Expand All @@ -32,6 +33,7 @@ def all_tests_names():
tests.extend(TestUtil.get_class_methods(IntegrationsTests))
tests.extend(TestUtil.get_class_methods(SeccompProfileTests))
tests.extend(TestUtil.get_class_methods(WorkflowsTests))
tests.extend(TestUtil.get_class_methods(RegistryTests))
return tests


Expand Down Expand Up @@ -66,6 +68,8 @@ def get_test(test_name):
return SeccompProfileTests().__getattribute__(test_name)()
if test_name in TestUtil.get_class_methods(WorkflowsTests):
return WorkflowsTests().__getattribute__(test_name)()
if test_name in TestUtil.get_class_methods(RegistryTests):
return RegistryTests().__getattribute__(test_name)()


ALL_TESTS = all_tests_names()
20 changes: 20 additions & 0 deletions configurations/system/tests_cases/registry_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import inspect
from os.path import join

from systest_utils.statics import DEFAULT_NOTIFICATIONS_PATHS, DEFAULT_NOTIFICATIONS_DEPLOYMENT_PATH, \
DEFAULT_REGISTRY_PATHS
from tests_scripts.users_notifications.alert_notifications import get_messages_from_teams_channel, \
enrich_teams_alert_channel, get_messages_from_slack_channel, enrich_slack_alert_channel
from .structures import TestConfiguration

class RegistryTests(object):

@staticmethod
def test_registry_scanning():
from tests_scripts.registry.registry_connectors import RegistryChecker
return TestConfiguration(
name=inspect.currentframe().f_code.co_name,
test_obj=RegistryChecker,
check_payload_file=join(DEFAULT_REGISTRY_PATHS, "check_{}.json"),
create_payload_file=join(DEFAULT_REGISTRY_PATHS, "create_quay.json") # we do sanity only for one provider
)
50 changes: 50 additions & 0 deletions infrastructure/backend_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class NotExistingCustomer(Exception):
API_NOTIFICATIONS_UNSUBSCRIBE = "/api/v1/notifications/unsubscribe"
API_NOTIFICATIONS_ALERTCHANNEL = "/api/v1/notifications/alertChannel"

API_REGISTRY_MANAGEMENT = "/api/v1/registry/management"

API_ATTACK_CHAINS = "/api/v1/attackchains"

API_NETWORK_POLICIES = "/api/v1/networkpolicies"
Expand Down Expand Up @@ -2179,6 +2181,54 @@ def remove_alert_channel(self, guid) -> requests.Response:
self.customer, res.status_code, res.text))
return res

def check_registry(self, payload, provider) -> requests.Response:
res = self.post(API_REGISTRY_MANAGEMENT + "/" + provider + "/repositories", cookies=self.selected_tenant_cookie, data=json.dumps(payload))
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: check registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def create_registry(self, payload, provider) -> requests.Response:
res = self.post(API_REGISTRY_MANAGEMENT + "/" + provider, cookies=self.selected_tenant_cookie, data=json.dumps(payload))
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: create registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def get_registry(self, provider, guid) -> requests.Response:
res = self.get(API_REGISTRY_MANAGEMENT + "/" + provider + "/" + guid, cookies=self.selected_tenant_cookie)
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: get registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def get_all_registries(self, provider) -> requests.Response:
res = self.get(API_REGISTRY_MANAGEMENT + "/" + provider, cookies=self.selected_tenant_cookie)
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: get registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def update_registry(self, payload, provider, guid) -> requests.Response:
res = self.put(API_REGISTRY_MANAGEMENT + "/" + provider + "/" + guid, cookies=self.selected_tenant_cookie, data=json.dumps(payload))
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: update registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def delete_registry(self, provider, guid) -> requests.Response:
res = self.delete(API_REGISTRY_MANAGEMENT + "/" + provider + "/" + guid, cookies=self.selected_tenant_cookie)
if not 200 <= res.status_code < 300:
raise Exception(
'Error accessing dashboard. Request: delete registry "%s" (code: %d, message: %s)' % (
self.customer, res.status_code, res.text))
return res

def get_attack_chains(self, cluster_name=None):
params = {"customerGUID": self.selected_tenant_id}

Expand Down
13 changes: 13 additions & 0 deletions system_test_mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,19 @@
"skip_on_environment": "production,production-us",
"owner": ""
},
"test_registry_scanning": {
"target": [
"In cluster",
"Backend"
],
"target_repositories": [
"cadashboardbe",
"event-ingester-service"
],
"description": "",
"skip_on_environment": "",
"owner": ""
},
"relevant_data_is_appended": {
"target": [
"In cluster",
Expand Down
3 changes: 3 additions & 0 deletions systest_utils/statics.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
DEFAULT_WORKFLOWS_PATHS = os.path.abspath(os.path.join('configurations', 'workflows_notifications'))
DEFAULT_WORKFLOWS_DEPLOYMENT_PATH = os.path.join(DEFAULT_WORKFLOWS_PATHS, 'deployments')

# registry
DEFAULT_REGISTRY_PATHS = os.path.abspath(os.path.join('configurations', 'registry'))

# kdr
DEFAULT_KDR_DEPLOYMENT_PATH = os.path.join(DEFAULT_K8S_PATHS, 'deployments')

Expand Down
Empty file.
14 changes: 14 additions & 0 deletions tests_scripts/registry/base_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from tests_scripts import base_test
from systest_utils import statics


class BaseRegistry(base_test.BaseTest):


def __init__(self, test_obj=None, backend=None, test_driver=None):
super().__init__(test_driver=test_driver, test_obj=test_obj, backend=backend)

def cleanup(self, **kwargs):
super().cleanup(**kwargs)
return statics.SUCCESS, ""

138 changes: 138 additions & 0 deletions tests_scripts/registry/registry_connectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import base64
import json
import os
from typing import List, Dict

from systest_utils import Logger, TestUtil, statics
from tests_scripts.helm.base_helm import BaseHelm

REGISTRY_PROVIDERS: List[Dict[str, any]] = [
{
"provider": "quay",
"secret_field_name": "robotAccountToken",
"secret_value_env_var": "QUAY_REGISTRY_ACCESS_TOKEN",
},
{
"provider": "aws",
"secret_field_name": "secretAccessKey",
"secret_value_env_var": "AWS_REGISTRY_SECRET_KEY",
},
{
"provider": "azure",
"secret_field_name": "accessToken",
"secret_value_env_var": "AZURE_REGISTRY_ACCESS_TOKEN",
},
{
"provider": "google",
"secret_field_name": "key",
"secret_value_env_var": "GOOGLE_REGISTRY_KEY",
},
]

class RegistryChecker(BaseHelm):

def __init__(self, test_obj=None, backend=None, kubernetes_obj=None, test_driver=None):
super(RegistryChecker, self).__init__(test_driver=test_driver, test_obj=test_obj, backend=backend,
kubernetes_obj=kubernetes_obj)

def start(self):
Logger.logger.info('Stage 1: Install kubescape with helm-chart')
cluster, _ = self.setup(apply_services=False)
self.install_kubescape()
Logger.logger.info('Stage 2: Check registries connection')
self.check_registries_connection(cluster)
Logger.logger.info('Stage 3: Check quay.io registry CRUD operations')
self.check_registry_crud(cluster)
return self.cleanup(cluster=cluster)

def check_registry_crud(self, cluster):
quay_config = REGISTRY_PROVIDERS[0]
provider = quay_config["provider"]

Logger.logger.info("Loading creation payload file")
file_path = self.test_obj["create_payload_file"]
with open(file_path, 'r') as file:
data = json.load(file)
data["clusterName"] = cluster
Logger.logger.info("Loading creation secret from env var")
secret = os.getenv(quay_config["secret_value_env_var"])
data[quay_config["secret_field_name"]] = secret

Logger.logger.info("Calling creation API")
created = self.backend.create_registry(data, provider)
assert created, "Expected created registry"
guid = created.json()["guid"]

Logger.logger.info("Checking if registry scan is completed")
self.assert_registry_scan_completed(provider, guid)

registry = self.backend.get_registry(provider, guid)
assert registry, "Expected to get registry"
update_payload = registry.json()
update_payload["scanFrequency"] = "30 * * 5 *"
Logger.logger.info("Updating registry with new scan scanFrequency")
updated = self.backend.update_registry(update_payload, provider, guid)
assert updated, "Expected updated registry"
assert updated.json()["scanFrequency"] == "30 * * 5 *", "Expected scanFrequency to be updated"

Logger.logger.info("Deleting registry")
self.backend.delete_registry(provider, guid)
registries = self.get_all_quay_registries_for_cluster(cluster)
assert len(registries) == 0, "Expected to have no registries"
self.cleanup(cluster=cluster)

def check_registries_connection(self, cluster):
for provider_config in REGISTRY_PROVIDERS:
provider = provider_config["provider"]
Logger.logger.info(f'{provider}: Loading payload file')
file_path = self.test_obj["check_payload_file"].format(provider)
with open(file_path, 'r') as file:
data = json.load(file)
data["clusterName"] = cluster
Logger.logger.info(f'{provider}: Loading secrets from env var')
secret = os.getenv(provider_config["secret_value_env_var"])
if provider == "google":
secret = json.loads(base64.b64decode(secret).decode('utf-8'))
data[provider_config["secret_field_name"]] = secret
Logger.logger.info(f'{provider}: Calling repositories API')
repositories_response = self.backend.check_registry(data, provider)
assert repositories_response, "Expected repositories"
assert any("systemtests/webgoat" in item for item in repositories_response.json()), \
f"'systemtests/webgoat' not found in any item of {repositories_response.json()}"

def cleanup(self, **kwargs):
self.delete_all_quay_registries_for_cluster(kwargs['cluster'])
return super().cleanup()

def install_kubescape(self, helm_kwargs: dict = None):
self.add_and_upgrade_armo_to_repo()
self.install_armo_helm_chart(helm_kwargs=helm_kwargs)
self.verify_running_pods(namespace=statics.CA_NAMESPACE_FROM_HELM_NAME)

def assert_registry_scan_completed(self, provider, guid):
TestUtil.sleep(30, "waiting for registry scan to complete")
for i in range(60):
try:
registry = self.backend.get_registry(provider, guid)
assert registry, "Expected registry"
assert registry.json()["scanStatus"] == "Completed"
break
except AssertionError:
if i == 59:
raise
TestUtil.sleep(2, "waiting for registry scan to complete")

def delete_all_quay_registries_for_cluster(self, cluster):
leftovers = self.get_all_quay_registries_for_cluster(cluster)
for r in leftovers:
self.backend.delete_registry(r["provider"], r["guid"])

def get_all_quay_registries_for_cluster(self, cluster):
ret = []
resp = self.backend.get_all_registries("quay").json()
if resp['total']['value'] == 0:
return ret
for r in resp['response']:
if r["clusterName"] == cluster:
ret.append(r)
return ret

0 comments on commit cf31851

Please sign in to comment.