From fabd7625ff0a20b8c99309130704a0eb1c673967 Mon Sep 17 00:00:00 2001 From: Anneke Breust <44376590+annekebr@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:07:40 +0200 Subject: [PATCH 1/4] fix: do not redundantly authenticate calls to notary Currently, for each TUF role the first request is simply to get the auth endpoint, the second is getting the actual auth token and only the third fetches the actual data. For four roles that makes 12 requests in total. Now, the auth token should be fetched beforehand (2 calls) resulting in 6 calls in total. --- connaisseur/validators/notaryv1/notary.py | 82 +++++++++++++------ .../validators/notaryv1/notaryv1_validator.py | 16 +++- helm/Chart.yaml | 4 +- scripts/get_root_key.py | 3 +- tests/conftest.py | 19 +++-- tests/data/notary/hanswurstnotary.yaml | 32 ++++++++ tests/validators/notaryv1/test_notary.py | 51 +++++++++++- .../notaryv1/test_notaryv1_validator.py | 2 + 8 files changed, 168 insertions(+), 41 deletions(-) create mode 100644 tests/data/notary/hanswurstnotary.yaml diff --git a/connaisseur/validators/notaryv1/notary.py b/connaisseur/validators/notaryv1/notary.py index 9bc557804..ebfc0bf91 100644 --- a/connaisseur/validators/notaryv1/notary.py +++ b/connaisseur/validators/notaryv1/notary.py @@ -89,24 +89,19 @@ def healthy(self): except Exception: return False - async def get_trust_data( - self, - session: aiohttp.ClientSession, - image: Image, - role: TUFRole, - token: str = None, - ): - im_repo = f"{image.repository}/" if image.repository else "" - url = ( - f"https://{self.host}/v2/{image.registry}/{im_repo}" - f"{image.name}/_trust/tuf/{str(role)}.json" - ) + # This function uses the endpoint to get the root trust data either to receive the root trust data + # directly in case the notary instance has no auth configured or to obtain the notary's auth + # endpoint (and then the auth token) first, and then use it for getting the trust data. + # To save network calls this is done in one function. + + async def get_root_trust_data_and_auth( + self, session: aiohttp.ClientSession, image: Image, token: str = None + ) -> (TrustData, str): + request_kwargs = self.__build_args(image, "root") + + if token: + request_kwargs.update({"headers": ({"Authorization": f"Bearer {token}"})}) - request_kwargs = { - "url": url, - "ssl": self.cert, - "headers": ({"Authorization": f"Bearer {token}"} if token else None), - } async with session.get(**request_kwargs) as response: status = response.status if ( @@ -119,9 +114,38 @@ async def get_trust_data( "www-authenticate" ] ) - token = await self.__get_auth_token(session, auth_url) - return await self.get_trust_data(session, image, role, token) + repo_token = await self.__get_auth_token(session, auth_url) + root_trust_data, _ = await self.get_root_trust_data_and_auth( + session, image, repo_token + ) + return root_trust_data, repo_token + elif status == 401 and not token: + msg = ( + "Unable to find authorization endpoint in" + "'www-authenticate' header for notary {notary_name}." + ) + raise NotFoundException(message=msg, notary_name=self.name) + else: + response.raise_for_status() + data = await response.text() + return TrustData(json.loads(data), "root"), token + async def get_trust_data( + self, + session: aiohttp.ClientSession, + image: Image, + role: TUFRole, + repo_token: str, + ): + request_kwargs = self.__build_args(image, str(role)) + + if repo_token: + request_kwargs.update( + {"headers": ({"Authorization": f"Bearer {repo_token}"})} + ) + + async with session.get(**request_kwargs) as response: + status = response.status if status == 404: msg = "Unable to get {tuf_role} trust data from {notary_name}." raise NotFoundException( @@ -137,10 +161,10 @@ async def get_delegation_trust_data( session: aiohttp.ClientSession, image: Image, role: TUFRole, - token: str = None, + repo_token: str, ): try: - return await self.get_trust_data(session, image, role, token) + return await self.get_trust_data(session, image, role, repo_token) except Exception as ex: if ConnaisseurLoggingWrapper.is_debug_level(): raise ex @@ -222,6 +246,7 @@ async def __get_auth_token(self, session: aiohttp.ClientSession, url: str): "ssl": self.cert, "auth": (aiohttp.BasicAuth(**self.auth) if self.auth else None), } + async with session.get(**request_kwargs) as response: if response.status >= 500: msg = "Unable to get authentication token from {auth_url}." @@ -233,7 +258,7 @@ async def __get_auth_token(self, session: aiohttp.ClientSession, url: str): try: token_key = "access_token" if self.is_acr else "token" - token = (await response.json(content_type=None))[token_key] + repo_token = (await response.json(content_type=None))[token_key] except KeyError as err: msg = ( "Unable to retrieve authentication token from {auth_url} response." @@ -246,7 +271,7 @@ async def __get_auth_token(self, session: aiohttp.ClientSession, url: str): r"^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$" # nosec ) - if not re.match(token_re, token): + if not re.match(token_re, repo_token): msg = "{validation_kind} has an invalid format." raise InvalidFormatException( message=msg, @@ -254,4 +279,13 @@ async def __get_auth_token(self, session: aiohttp.ClientSession, url: str): notary_name=self.name, auth_url=url, ) - return token + return repo_token + + def __build_args(self, image: Image, role: str): + im_repo = f"{image.repository}/" if image.repository else "" + url = ( + f"https://{self.host}/v2/{image.registry}/{im_repo}" + f"{image.name}/_trust/tuf/{role}.json" + ) + + return {"url": url, "ssl": self.cert} diff --git a/connaisseur/validators/notaryv1/notaryv1_validator.py b/connaisseur/validators/notaryv1/notaryv1_validator.py index a966ef69d..9a6e7528c 100644 --- a/connaisseur/validators/notaryv1/notaryv1_validator.py +++ b/connaisseur/validators/notaryv1/notaryv1_validator.py @@ -118,12 +118,20 @@ async def __process_chain_of_trust( # load all trust data t_start = dt.datetime.now() + + # even if a registry is public like e.g. docker.io, it might still + # require a token on repository level, specifying the scope (e.g. pull) + root_trust_data, repo_token = await self.notary.get_root_trust_data_and_auth( + session, image + ) trust_data_list = await asyncio.gather( *[ - self.notary.get_trust_data(session, image, TUFRole(role)) + self.notary.get_trust_data(session, image, TUFRole(role), repo_token) for role in tuf_roles + if role != "root" ] ) + trust_data_list[:0] = [root_trust_data] duration = (dt.datetime.now() - t_start).total_seconds() logging.debug("Pulled trust data for image %s in %s seconds.", image, duration) trust_data = {tuf_roles[i]: trust_data_list[i] for i in range(len(tuf_roles))} @@ -185,7 +193,7 @@ async def __process_chain_of_trust( # download only the required delegation files await self.__update_with_delegation_trust_data( - session, trust_data, req_delegations, key_store, image + session, trust_data, req_delegations, key_store, image, repo_token ) # if certain delegations are required, then only take the targets fields of the @@ -263,12 +271,12 @@ def __search_image_targets(trust_data: dict, image: Image): return None async def __update_with_delegation_trust_data( - self, session: ClientSession, trust_data, delegations, key_store, image + self, session: ClientSession, trust_data, delegations, key_store, image, token ): delegation_trust_data_list = await asyncio.gather( *[ self.notary.get_delegation_trust_data( - session, image, TUFRole(delegation) + session, image, TUFRole(delegation), token ) for delegation in delegations ] diff --git a/helm/Chart.yaml b/helm/Chart.yaml index e6aa9a80f..f3660c9fd 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: connaisseur description: Helm chart for Connaisseur - a Kubernetes admission controller to integrate container image signature verification and trust pinning into a cluster. type: application -version: 2.3.0 -appVersion: 3.3.0 +version: 2.3.1 +appVersion: 3.3.1 keywords: - container image - signature diff --git a/scripts/get_root_key.py b/scripts/get_root_key.py index 5179fba4b..f22b26261 100644 --- a/scripts/get_root_key.py +++ b/scripts/get_root_key.py @@ -15,7 +15,8 @@ async def get_pub_root_key(host: str, image: Image): notary = Notary("no", host, ["not_empty"]) async with (aiohttp.ClientSession()) as session: - root_td = await notary.get_trust_data(session, image, TUFRole("root")) + token = await notary.get_auth(session, image) + root_td = await notary.get_trust_data(session, image, TUFRole("root"), token) root_key_id = root_td.signatures[0].get("keyid") root_cert_base64 = ( diff --git a/tests/conftest.py b/tests/conftest.py index 535674f41..f0237c4ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -142,7 +142,16 @@ def mock_request_notary(match: re.Match, **kwargs): match.group(5), ) - if registry == "auth.io" and not kwargs.get("headers"): + if kwargs.get("headers") and kwargs.get("headers").get("Authorization"): + if registry == "empty.io": + return MockResponse({}, status_code=404) + + return MockResponse(get_td(f"{image}/{role}")) + elif registry == "empty.io": + return MockResponse({}, status_code=401) + elif registry == "notary_wo_auth.io": + return MockResponse(get_td(f"{image}/{role}")) + else: return MockResponse( {}, headers={ @@ -153,10 +162,6 @@ def mock_request_notary(match: re.Match, **kwargs): }, status_code=401, ) - if registry == "empty.io": - return MockResponse({}, status_code=404) - - return MockResponse(get_td(f"{image}/{role}")) def mock_request_kube(match: re.Match, **kwargs): @@ -399,12 +404,12 @@ def m_alerting_without_send(monkeypatch, m_safe_path_func, mocker): @pytest.fixture def count_loaded_delegations(monkeypatch): - async def get_delegation_trust_data_counted(self, image, role, token=None): + async def get_delegation_trust_data_counted(self, session, image, role, token): monkeypatch.setenv( "DELEGATION_COUNT", str(int(os.getenv("DELEGATION_COUNT")) + 1) ) try: - return await no.Notary.get_trust_data(self, image, role, token) + return await no.Notary.get_trust_data(self, session, image, role, token) except Exception as ex: if os.environ.get("LOG_LEVEL", "INFO") == "DEBUG": raise ex diff --git a/tests/data/notary/hanswurstnotary.yaml b/tests/data/notary/hanswurstnotary.yaml new file mode 100644 index 000000000..7090f9004 --- /dev/null +++ b/tests/data/notary/hanswurstnotary.yaml @@ -0,0 +1,32 @@ +name: harbor +host: notary.hans.io +cert: | + -----BEGIN CERTIFICATE----- + MIIDEzCCAfugAwIBAgIQEHy1Je1mbrt0RaLDjDajszANBgkqhkiG9w0BAQsFADAU + MRIwEAYDVQQDEwloYXJib3ItY2EwHhcNMjEwMTI2MTQyNTE5WhcNMjIwMTI2MTQy + NTE5WjAUMRIwEAYDVQQDEwloYXJib3ItY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB + DwAwggEKAoIBAQCfy2A79g4KGx1BN8LgNwF34pSJaKqzV9hsanNKi5iU6Sn2Qrjx + a++HlCYK8TAZ54cacP1T+d+eqlDwgMlbkXsjSFiRr3Z+KxtrrFbM9yNrNzyUiDVW + czUQM+PFEETk2uwp7GSHFFBXeo+6p/cI2vqSqxpkVVojKmX6vEdEdPh9mwBt9nuk + MNfaJxzzjpAPdH9TkWME+J+GpxuLhtRnE0PStC6ioYI4FeH5MCwLKv7ZVyxWYDpY + f5qG2H00rGNOHsq9jidyLbp90jboMbVHMO6ragM6sqrjPF/cLE8oifuguCR6dbVk + yQuIacfG/vglnp5juvFLDmf0ZVBytazWMUQzAgMBAAGjYTBfMA4GA1UdDwEB/wQE + AwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw + AwEB/zAdBgNVHQ4EFgQUwtWtGfG+NU6ZcqhJI+lKRHOW/qQwDQYJKoZIhvcNAQEL + BQADggEBABiBHCuadw+SlmQHuK9egZSzIjvaLdKcTWdYwICtzuymZyyAWxWGeY8O + ZRZ9ZvsVX8jgTsSlFe+nV/+3MokYCvCaaDmyre7zZmRsq65ILSrwJMWjSqyvt8/X + s78uvGgi8/ooP7eldlduOA3AdV81Ty9GeCWWqEVIjEgfVQhpYquNTyOcUp8Tuks6 + 5OkY1pS58NRkoIM6/jfGtgbzsvvHooZwslmq8eaT+MucuzuGpY2GelEE5pI9Q7tf + hMC42zeU+yxxy3vukMa4xX2BGzyjAg+qaDh6YwWui80r2/BlYXvSsSl3dIgtVwL4 + DSo1s+3uJ4evVKDRf3vLwKLTtiYfd20= + -----END CERTIFICATE----- +trustRoots: + - name: library + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH+G0yM9CQ8KjN2cF8iHpiTA9Q69q + 3brrzLkY1kjmRNOs0c2sx2nm8j2hFZRbyaVsd52Mkw0k5WrX+9vBfbjtdQ== + -----END PUBLIC KEY----- +auth: + username: hans + password: wurst \ No newline at end of file diff --git a/tests/validators/notaryv1/test_notary.py b/tests/validators/notaryv1/test_notary.py index 157d42345..be2353d97 100644 --- a/tests/validators/notaryv1/test_notary.py +++ b/tests/validators/notaryv1/test_notary.py @@ -18,7 +18,7 @@ def sample_notaries(): notary.Notary.CERT_PATH = "tests/data/notary/{}.cert" li = [] - for file_name in ("notary1", "notary2", "unhealthy_notary"): + for file_name in ("notary1", "notary2", "unhealthy_notary", "hanswurstnotary"): with open(f"tests/data/notary/{file_name}.yaml") as file: li.append(yaml.safe_load(file)) return li @@ -122,6 +122,49 @@ def test_healthy(sample_notaries, m_request, index, host, health): assert no.healthy == health +@pytest.mark.asyncio +@pytest.mark.parametrize( + "index, image, output, exception", + [ + ( + 3, + "alice-image", + (fix.get_td("alice-image/root"), "a.valid.token"), + fix.no_exc(), + ), + (0, "empty.io/alice-image", {}, pytest.raises(exc.NotFoundException)), + ( + 0, + "notary_wo_auth.io/alice-image", + (fix.get_td("alice-image/root"), None), + fix.no_exc(), + ), + ], +) +async def test_get_root_trust_data_and_auth( + monkeypatch, + sample_notaries, + m_request, + index, + image, + output, + exception, +): + async with aiohttp.ClientSession() as session: + with exception: + with aioresponses() as aio: + aio.get(re.compile(r".*"), callback=fix.async_callback, repeat=True) + no = notary.Notary(**sample_notaries[index]) + trust_data, token = await no.get_root_trust_data_and_auth( + session, + Image(image), + ) + (expected_trust_data, expected_token) = output + assert token == expected_token + assert trust_data.signed == expected_trust_data["signed"] + assert trust_data.signatures == expected_trust_data["signatures"] + + @pytest.mark.asyncio @pytest.mark.parametrize( "index, image, role, output, exception", @@ -151,9 +194,10 @@ async def test_get_trust_data( async with aiohttp.ClientSession() as session: with exception: with aioresponses() as aio: + token = "***" aio.get(re.compile(r".*"), callback=fix.async_callback, repeat=True) no = notary.Notary(**sample_notaries[index]) - td = await no.get_trust_data(session, Image(image), role) + td = await no.get_trust_data(session, Image(image), role, token) assert td.signed == output["signed"] assert td.signatures == output["signatures"] @@ -190,10 +234,11 @@ async def test_get_delegation_trust_data( async with aiohttp.ClientSession() as session: with exception: with aioresponses() as aio: + token = "***" aio.get(re.compile(r".*"), callback=fix.async_callback) no = notary.Notary(**sample_notaries[index]) td = await no.get_delegation_trust_data( - session, Image(image), "targets/phbelitz" + session, Image(image), "targets/phbelitz", token ) assert output is bool(td) diff --git a/tests/validators/notaryv1/test_notaryv1_validator.py b/tests/validators/notaryv1/test_notaryv1_validator.py index 6cfb89e7a..7138f3086 100644 --- a/tests/validators/notaryv1/test_notaryv1_validator.py +++ b/tests/validators/notaryv1/test_notaryv1_validator.py @@ -424,6 +424,7 @@ async def test_update_with_delegation_trust_data( delegations, expected_trust_data_keys, ): + token = "ey123" async with aiohttp.ClientSession() as session: with aioresponses() as aio: aio.get(re.compile(r".*"), callback=fix.async_callback, repeat=True) @@ -435,6 +436,7 @@ async def test_update_with_delegation_trust_data( delegations, alice_key_store, Image("alice-image"), + token, ) is None ) From 39b051fc9776dbcb301f426605c1261f10ce7f39 Mon Sep 17 00:00:00 2001 From: Anneke Breust <44376590+annekebr@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:18:15 +0100 Subject: [PATCH 2/4] docs: Fix testing instructions --- docs/CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index bb7822eba..a64aeeed1 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -50,10 +50,9 @@ A simple starting point may be a minikube cluster with e.g. a [Docker Hub](https In case you make changes to the Connaisseur container image itself or code for that matter, you need to re-build the image and install it locally for testing. This requires a few steps: -1. In `helm/values.yaml`, set `imagePullPolicy` to `IfNotPresent`. -2. Configure your local environment to use the Kubernetes Docker daemon. In minikube, this can be done via `eval (minikube docker-env)`. -3. Build the Connaisseur container image via `make docker`. -4. Install Connaisseur as usual via `make install`. +1. Configure your local environment to use the Kubernetes Docker daemon. In minikube, this can be done via `eval $(minikube docker-env)`. +2. Build the Connaisseur container image via `make docker`. +3. Install Connaisseur via `make dev-install`. ### Test changes Tests and linting are important to ensure code quality, functionality and security. From 160750c169717f03c08568b97204fdca1e4563eb Mon Sep 17 00:00:00 2001 From: Anneke Breust <44376590+annekebr@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:32:42 +0100 Subject: [PATCH 3/4] test: add integration test for self hosted notary without auth --- .github/actions/alerting-endpoint/action.yml | 2 +- .github/actions/notary-server-ip/action.yml | 19 +++++ .github/actions/notary-signer-ip/action.yaml | 19 +++++ .github/actions/setup-notary/action.yaml | 32 +++++++ .../workflows/.reusable-integration-test.yml | 83 ++++++++++++++++++- .github/workflows/cicd.yaml | 2 +- .github/workflows/release.yaml | 2 +- tests/data/notary/hanswurstnotary.yaml | 2 +- .../config/client_config.json | 6 ++ .../notary_service_container/server/ca.crt | 19 +++++ .../server/config.json | 19 +++++ .../server/notary-server.crt | 18 ++++ .../server/notary-server.key | 28 +++++++ .../notary_service_container/signer/ca.crt | 19 +++++ .../signer/config.json | 11 +++ .../signer/notary-signer.crt | 18 ++++ .../signer/notary-signer.key | 28 +++++++ tests/integration/cases.yaml | 17 ++++ tests/integration/integration-test.sh | 32 +++++++ tests/integration/notary_addhash.sh | 8 ++ tests/integration/notary_init.sh | 16 ++++ .../update-self-hosted-notary.yaml | 66 +++++++++++++++ .../update_deployment_notary_test.yaml | 4 + 23 files changed, 462 insertions(+), 8 deletions(-) create mode 100644 .github/actions/notary-server-ip/action.yml create mode 100644 .github/actions/notary-signer-ip/action.yaml create mode 100644 .github/actions/setup-notary/action.yaml create mode 100644 tests/data/notary_service_container/config/client_config.json create mode 100644 tests/data/notary_service_container/server/ca.crt create mode 100644 tests/data/notary_service_container/server/config.json create mode 100644 tests/data/notary_service_container/server/notary-server.crt create mode 100644 tests/data/notary_service_container/server/notary-server.key create mode 100644 tests/data/notary_service_container/signer/ca.crt create mode 100644 tests/data/notary_service_container/signer/config.json create mode 100644 tests/data/notary_service_container/signer/notary-signer.crt create mode 100644 tests/data/notary_service_container/signer/notary-signer.key create mode 100755 tests/integration/notary_addhash.sh create mode 100755 tests/integration/notary_init.sh create mode 100644 tests/integration/update-self-hosted-notary.yaml create mode 100644 tests/integration/update_deployment_notary_test.yaml diff --git a/.github/actions/alerting-endpoint/action.yml b/.github/actions/alerting-endpoint/action.yml index 0bb4be13d..97e44cb07 100644 --- a/.github/actions/alerting-endpoint/action.yml +++ b/.github/actions/alerting-endpoint/action.yml @@ -13,7 +13,7 @@ runs: CONTAINER=$(docker container ls --no-trunc --format "{{json . }}" | jq ' . | select(.Image|match("alerting-endpoint"))') CONTAINER_ID=$(echo ${CONTAINER} | jq -r .ID) CONTAINER_NETWORK=$(echo ${CONTAINER} | jq -r .Networks) - SEARCH_PATH=.[0].NetworkSettings.Networks.${CONTAINER_NETWORK}.IPAddress + SEARCH_PATH=.[0].NetworkSettings.Networks."${CONTAINER_NETWORK}".IPAddress IP=$(docker container inspect ${CONTAINER_ID} | jq -r ${SEARCH_PATH}) echo IP=${IP} >> ${GITHUB_OUTPUT} shell: bash diff --git a/.github/actions/notary-server-ip/action.yml b/.github/actions/notary-server-ip/action.yml new file mode 100644 index 000000000..b6cbf32ad --- /dev/null +++ b/.github/actions/notary-server-ip/action.yml @@ -0,0 +1,19 @@ +name: get-ips-of-service-containers +description: 'Get notary server IP' +outputs: + notary_ip: + description: "IP address of the notary instance" + value: ${{ steps.get_notary_server_ip.outputs.notary_ip }} +runs: + using: "composite" + steps: + - name: Get IP + id: get_notary_server_ip + run: | + NOTARY_CONTAINER=$(docker container ls --no-trunc --format "{{json . }}" | jq ' . | select(.Image|match("notary:server"))') + NOTARY_CONTAINER_ID=$(echo ${NOTARY_CONTAINER} | jq -r .ID) + NOTARY_CONTAINER_NETWORK=$(echo ${NOTARY_CONTAINER} | jq -r .Networks) + NOTARY_SEARCH_PATH=.[0].NetworkSettings.Networks."${NOTARY_CONTAINER_NETWORK}".IPAddress + NOTARY_IP=$(docker container inspect ${NOTARY_CONTAINER_ID} | jq -r ${NOTARY_SEARCH_PATH}) + echo notary_ip=${NOTARY_IP} >> ${GITHUB_OUTPUT} + shell: bash diff --git a/.github/actions/notary-signer-ip/action.yaml b/.github/actions/notary-signer-ip/action.yaml new file mode 100644 index 000000000..95f22785e --- /dev/null +++ b/.github/actions/notary-signer-ip/action.yaml @@ -0,0 +1,19 @@ +name: get-ip-of-notary-signer-container +description: 'Get notary-signer IP' +outputs: + notary_signer_ip: + description: "IP address of the notary signer instance" + value: ${{ steps.get_signer_ip.outputs.notary_signer_ip }} +runs: + using: "composite" + steps: + - name: Get notary signer IP + id: get_signer_ip + run: | + NOTARY_CONTAINER=$(docker container ls --no-trunc --format "{{json . }}" | jq ' . | select(.Image|match("notary:signer"))') + NOTARY_CONTAINER_ID=$(echo ${NOTARY_CONTAINER} | jq -r .ID) + NOTARY_CONTAINER_NETWORK=$(echo ${NOTARY_CONTAINER} | jq -r .Networks) + NOTARY_SEARCH_PATH=.[0].NetworkSettings.Networks."${NOTARY_CONTAINER_NETWORK}".IPAddress + NOTARY_IP=$(docker container inspect ${NOTARY_CONTAINER_ID} | jq -r ${NOTARY_SEARCH_PATH}) + echo notary_signer_ip=${NOTARY_IP} >> ${GITHUB_OUTPUT} + shell: bash diff --git a/.github/actions/setup-notary/action.yaml b/.github/actions/setup-notary/action.yaml new file mode 100644 index 000000000..17017eff0 --- /dev/null +++ b/.github/actions/setup-notary/action.yaml @@ -0,0 +1,32 @@ +name: setup-notary-trust-data +description: 'Initialize trust data in running notary service container for existing test image' +outputs: + root_key: + description: "Root key of the securesystemsengineering repository in the ephemeral self-hosted notary instance" + value: ${{ steps.get_root_key.outputs.root_key }} +runs: + using: "composite" + steps: + - name: Install notary and docker client and expect + run: | + sudo apt install docker + sudo apt install notary + sudo apt install expect + shell: bash + - name: Trust root cert of notary instance + run: | + sudo cp ./tests/data/notary_service_container/server/ca.crt /usr/local/share/ca-certificates/notary_root_ca.crt + sudo update-ca-certificates + shell: bash + - name: Append notary ip to /etc/hosts + run: | + sudo -- sh -c "echo '${NOTARY_IP} notary.server' >> /etc/hosts" + shell: bash + - name: Configure notary client + run: | + ./tests/integration/notary_init.sh + docker pull docker.io/securesystemsengineering/testimage:self-hosted-notary-signed + DIGEST=$(docker images --digests | grep self-hosted-notary-signed | awk '{print $3}') + export DIGEST_WITHOUT_PREFIX=$(echo ${DIGEST#sha256:}) + ./tests/integration/notary_addhash.sh ${DIGEST_WITHOUT_PREFIX} + shell: bash diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml index 5df9db09b..715eb7d87 100644 --- a/.github/workflows/.reusable-integration-test.yml +++ b/.github/workflows/.reusable-integration-test.yml @@ -75,13 +75,13 @@ jobs: with: k8s-version: v1.25 - name: Get alerting endpoint IP - id: get_ip + id: get_alerting_endpoint_ip uses: ./.github/actions/alerting-endpoint - name: Run test run: | bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" env: - ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} + ALERTING_ENDPOINT_IP: ${{ steps.get_alerting_endpoint_ip.outputs.ip }} - name: Display Connaisseur configuration if: always() run: | @@ -141,13 +141,13 @@ jobs: with: k8s-version: v1.25 - name: Get alerting endpoint IP - id: get_ip + id: get_alerting_endpoint_ip uses: ./.github/actions/alerting-endpoint - name: Run test run: | bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" env: - ALERTING_ENDPOINT_IP: ${{ steps.get_ip.outputs.ip }} + ALERTING_ENDPOINT_IP: ${{ steps.get_alerting_endpoint_ip.outputs.ip }} - name: Display Connaisseur configuration if: always() run: | @@ -164,6 +164,81 @@ jobs: run: | kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + self-hosted-notary: + name: self-hosted-notary + runs-on: ubuntu-latest + if: | + inputs.skip_integration_tests != 'self-hosted-notary' && + inputs.skip_integration_tests != 'non-required' && + inputs.skip_integration_tests != 'all' + permissions: + packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + COSIGN_PUBLIC_KEY: ${{ inputs.cosign_public_key }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "self-hosted-notary" + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - name: Setup notary signer instance + run: | + docker run -d -p 7899:7899 -v ./tests/data/notary_service_container/signer:/etc/docker/notary-signer/ notary:signer -config=/etc/docker/notary-signer/config.json + - name: Get notary signer instance IP + id: get_notary_signer_ip + uses: ./.github/actions/notary-signer-ip + - name: Setup notary server instance + run: | + docker run -d -p 4443:4443 --add-host notary.signer:${{ steps.get_notary_signer_ip.outputs.notary_signer_ip }} -v ./tests/data/notary_service_container/server:/etc/docker/notary-server notary:server -config=/etc/docker/notary-server/config.json -logf=json + - name: Get container IPs + id: get_notary_server_ip + uses: ./.github/actions/notary-server-ip + - name: Populate notary instance with trust data + uses: ./.github/actions/setup-notary + id: setup_notary + env: + NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.25 + - name: Run test + run: | + bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" + env: + NOTARY_IP: ${{ steps.get_notary_server_ip.outputs.notary_ip }} + - name: Display Connaisseur configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' helm/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n connaisseur -lapp.kubernetes.io/name=connaisseur + kubectl describe pods -n connaisseur -lapp.kubernetes.io/name=connaisseur + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n connaisseur -lapp.kubernetes.io/name=connaisseur --prefix=true --tail=-1 + + k8s-versions: name: k8s versions runs-on: ubuntu-latest diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml index c7455a681..cb4886d8c 100644 --- a/.github/workflows/cicd.yaml +++ b/.github/workflows/cicd.yaml @@ -17,7 +17,7 @@ defaults: shell: bash env: - SKIP_INTEGRATION_TESTS: 'none' # 'none', 'non-required', 'all' + SKIP_INTEGRATION_TESTS: 'self-hosted-notary' # 'none', 'non-required', 'all', 'self-hosted-notary' jobs: conditionals: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d874a2ada..ae231aa74 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ defaults: shell: bash env: - SKIP_INTEGRATION_TESTS: 'non-required' # 'none', 'non-required', 'all' + SKIP_INTEGRATION_TESTS: 'none' # 'none', 'non-required', 'all', 'self-hosted-notary' jobs: conditionals: diff --git a/tests/data/notary/hanswurstnotary.yaml b/tests/data/notary/hanswurstnotary.yaml index 7090f9004..fbbff5792 100644 --- a/tests/data/notary/hanswurstnotary.yaml +++ b/tests/data/notary/hanswurstnotary.yaml @@ -29,4 +29,4 @@ trustRoots: -----END PUBLIC KEY----- auth: username: hans - password: wurst \ No newline at end of file + password: wurst diff --git a/tests/data/notary_service_container/config/client_config.json b/tests/data/notary_service_container/config/client_config.json new file mode 100644 index 000000000..b98c43a2b --- /dev/null +++ b/tests/data/notary_service_container/config/client_config.json @@ -0,0 +1,6 @@ +{ + "remote_server": { + "url": "https://notary.server:4443", + "root_ca": "../server/ca.crt" + } +} diff --git a/tests/data/notary_service_container/server/ca.crt b/tests/data/notary_service_container/server/ca.crt new file mode 100644 index 000000000..43109fd65 --- /dev/null +++ b/tests/data/notary_service_container/server/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIUDDvwp5mG7ckbcvJ0jg9he/rSGzAwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOTm90YXJ5IFJvb3QgQ0EwHhcNMjMxMTIyMjIwOTM5WhcN +MjQxMTIxMjIwOTM5WjAZMRcwFQYDVQQDDA5Ob3RhcnkgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6IjUiNqWDrwO1Yn/Yfc5hSUdH3ZZ1i +SzbsYL4ZL3ZN/rXv8rS3JBeNpbcRs85lYIjC8prCh9tJ6FjnMqUVI5pGz9XZPLNO +hQA4eW4Z5lHDd1oWCT1Y66zwUYSD59xUzGWCZ5OhZlKanu8OfdGDyoPJxKYmhZQz +Vnnh1chZC5eBBzObufWO47aMg4rX7ZLCER/HxLHfSnCVMVwOa3KBHDK3yWy+y86y +7/0bl5iW/bR8OZ/cjH4eEN9QdbAYBsavEsJ6wxBDgdweUH+7fQfORn5/IJtqwGha +FAyfeTwRI8/8tA4J1U08/p8oIopbzcGTg1LjPHQ8s8RTtknzg+jTrWkCAwEAAaNT +MFEwHQYDVR0OBBYEFKkx59QcInNCTOluvzHt3frciFqtMB8GA1UdIwQYMBaAFKkx +59QcInNCTOluvzHt3frciFqtMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAKyPiDT0PXTHzpC7sLq7s2pUe3ezqg51m4NTgynO3u+vYftTCMVDIV0P +H7ZJNyHHYvglaLjRnsWdjGs4tcqjIYCRL8Tnc/CVorI0QGGwDDlvWXNXleAxjV3t +t5hy7t5JpnNgF2GYNnSeuYG21rDDcP9EQZKv1a2KaKcgFRtzktlegRX0kGjDxbYe +0jo5j4oJZ944W79GqcQLeRpOU8PnbSaBtRPSeX1VhzpkR3pQy5FgBikQkvRP/dJw +H5Ck1P7fRzDnlv4cypSk6v9PWSQMrSYlyp1a40b9U1bIF2cQ+HVDwUg+56OPAey6 +wBELObfROXagGdjRP+OQJUJgFHS3Uss= +-----END CERTIFICATE----- diff --git a/tests/data/notary_service_container/server/config.json b/tests/data/notary_service_container/server/config.json new file mode 100644 index 000000000..fc2820d15 --- /dev/null +++ b/tests/data/notary_service_container/server/config.json @@ -0,0 +1,19 @@ +{ + "server": { + "http_addr": ":4443", + "tls_key_file": "./notary-server.key", + "tls_cert_file": "./notary-server.crt" + }, + "trust_service": { + "type": "remote", + "hostname": "notary.signer", + "port": "7899", + "tls_ca_file": "./ca.crt", + "key_algorithm": "ecdsa", + "tls_client_cert": "./notary-server.crt", + "tls_client_key": "./notary-server.key" + }, + "storage": { + "backend": "memory" + } +} diff --git a/tests/data/notary_service_container/server/notary-server.crt b/tests/data/notary_service_container/server/notary-server.crt new file mode 100644 index 000000000..fe41f71e8 --- /dev/null +++ b/tests/data/notary_service_container/server/notary-server.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdKgAwIBAgIURG6fK3XvIY0hybWB9UUl016hmOEwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOTm90YXJ5IFJvb3QgQ0EwHhcNMjMxMTIyMjIxMzE0WhcN +MjQxMTIxMjIxMzE0WjAYMRYwFAYDVQQDDA1ub3Rhcnkuc2VydmVyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU7Oxo0F4s4m8okLxbjM7oYuSlbSf8JC +CUge9PRMf9fV/xGKz4mpphy5Gb0c84RV774HL+Qv+3lvdMN41RdLlopmoIsK2sJY +SZB1j44ywQGFXmYRJf2hpeNXG8nC52iWZaFgg4HZFlRvjD8dJc7CTv/XX2AdYS5q +16Y1AAfg7eAK4FOyScBaHGb6SQOuEdjPBDckYH0ZR6bz/Ihj3PR2oSg5IYQEth20 +zSMvI8A2N9b780+9C9WCBq63Wf4Qlblm2s4UyinsOyv7wpTo+ZzyCQrcPQ7aynPE +JwDing57uM6WLy6s7kMQzORGsvyOUqz1RLHQAUhqt994P5Vn7u3JPQIDAQABoysw +KTAnBgNVHREEIDAegg1ub3Rhcnktc2VydmVygg1ub3Rhcnkuc2VydmVyMA0GCSqG +SIb3DQEBCwUAA4IBAQBXHYBY8dfZ8q9DG6TDFWXAT7Lll7akRWo0zDCLoEHAnIJ4 +tVd5MmEySDCd9Z8/YPZPxX+3p1q5fbquDdD6dlNip98Db1h+n/JreqZBpa4Jbzef +/dhKhnxD1Mf9FXsLse1lb0YqJGNUXIIBWUGPInIKPiL0SjCI7wuvKPVtMLUoa86Q +rYsMWmiWsGXMySK89Yf+egrTLT0c7Jv+A5dfoYJlouhGxlUksrgJj38SBgNHdqiY +IYqrcurSvaEpWHRQTdbBsYQXrzhaIPTGes3n3kY5ZJG1K1+Z5RnFVkQJizAyGXwq +RLnRS3Ajc8Ntq7FdpIuzZNxJwWVFhTs5jJpDW4l6 +-----END CERTIFICATE----- diff --git a/tests/data/notary_service_container/server/notary-server.key b/tests/data/notary_service_container/server/notary-server.key new file mode 100644 index 000000000..4b36a0ad0 --- /dev/null +++ b/tests/data/notary_service_container/server/notary-server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtTs7GjQXiziby +iQvFuMzuhi5KVtJ/wkIJSB709Ex/19X/EYrPiammHLkZvRzzhFXvvgcv5C/7eW90 +w3jVF0uWimagiwrawlhJkHWPjjLBAYVeZhEl/aGl41cbycLnaJZloWCDgdkWVG+M +Px0lzsJO/9dfYB1hLmrXpjUAB+Dt4ArgU7JJwFocZvpJA64R2M8ENyRgfRlHpvP8 +iGPc9HahKDkhhAS2HbTNIy8jwDY31vvzT70L1YIGrrdZ/hCVuWbazhTKKew7K/vC +lOj5nPIJCtw9DtrKc8QnAOKeDnu4zpYvLqzuQxDM5Eay/I5SrPVEsdABSGq333g/ +lWfu7ck9AgMBAAECggEAFuEskzHS8RpeMe69yyCWjXbRbacAqzUuGqOW0qfd4ZLR +AZeDR9rYtsFb/nXk+JEj9z6THFN3si9Z84RK03b716f13IP+rtqxZSTZIFaZhLl6 +rfezk9xkFs5olZaRUJOSJ2CiqTnfOv9yqoU6h98/78X+3OyHdGj4WffrT1G69CxD +8Z87HRXaSc3JTOSLqoRBpQYX7CJF91zfYfnwoj0O0tp6OF+ip8Q59ia/uaXNvPrA +6Jpc/0BW06DC4O/r4qEtF4VyRMHqTB106ZHj8gEhfrdIzi/9+uYEmA0jav6ywpTF +1P3wz0UXdy2LxJ8eMF+b44uSiVXUmKsKHAtbaNy3UQKBgQDlcpsU9jXlOh9DT8jS +KQLxHeBW/8st63Mc4nDrM/1ycOe1i1GpbxQsigWQvNXjUPs4E0N4vfdHq8ERPjkJ +HAaKu7eSooE5msmiFUM0f+RtqsDOQjVwrPMRii59VYrl2H6j9MPAVxzZ55RN6JDL +cBAfFTe2pLg14gPOOybWi9oc9wKBgQDBXQ7BLaOQ8an5pWFtChhghlyS2Ek7cACC +/HbJm+VZpI49rGRy90y0z7MLZPn2+gRsT+oD3yrcJXHGuVWiNQWY/ncmmhob5s/q +c/PsdciCeqvUTEDZ39ccenKpsCEuZZfGYovkRRLXfo0S4gGh/3AXTiWIWdSO1DY9 +wBkh5BRCawKBgQCfe56h3lUt5M1wKxfKRGlgEUUrE5c1bs/PhvG3+qYzEplsDc+Q +Nr5OCw29QhRlcZd8rZ8bYOdtcMu84YQhedJuQfZiPQQXdyipuZ/B8RdkxuhHNawR +ipVVXrfbtEbcZjP4YJxjp+lM6POjh4CFd3otMMFN+YZ1JYlBosnnHMRZlwKBgH2Y +PCUtx7g1v/nvecChdgP8QdT/t1FsBmkOIvoA0I/RWrKkbvpdtu0am2kjRVkuPAE+ +RvoM0oH7sFMrvnuFhQVDA5GHNr44xYO7nQxR1NMrasCSZu0df1N4FVIynNrOEi9B +gyvZ0cs239sMAZN/nwcEM0zFTFMZc8HYLHre8mnRAoGAc32wPM3dZHw1PtgmYf2l +WU3CCCm0PdTkQrC4lFyCATWzEwA9MOhvxlCctNnjyj+nRn+SLCos1Ygy+gSnIAnj +gfKJpavtG0KITS7rtCV8DukLDYXApksL/WpEVerA2F2spOtv+MhCIxJ+dSvO5q3S +FRd6yZrHlagfJwXN/ZuXxq4= +-----END PRIVATE KEY----- diff --git a/tests/data/notary_service_container/signer/ca.crt b/tests/data/notary_service_container/signer/ca.crt new file mode 100644 index 000000000..43109fd65 --- /dev/null +++ b/tests/data/notary_service_container/signer/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEzCCAfugAwIBAgIUDDvwp5mG7ckbcvJ0jg9he/rSGzAwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOTm90YXJ5IFJvb3QgQ0EwHhcNMjMxMTIyMjIwOTM5WhcN +MjQxMTIxMjIwOTM5WjAZMRcwFQYDVQQDDA5Ob3RhcnkgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6IjUiNqWDrwO1Yn/Yfc5hSUdH3ZZ1i +SzbsYL4ZL3ZN/rXv8rS3JBeNpbcRs85lYIjC8prCh9tJ6FjnMqUVI5pGz9XZPLNO +hQA4eW4Z5lHDd1oWCT1Y66zwUYSD59xUzGWCZ5OhZlKanu8OfdGDyoPJxKYmhZQz +Vnnh1chZC5eBBzObufWO47aMg4rX7ZLCER/HxLHfSnCVMVwOa3KBHDK3yWy+y86y +7/0bl5iW/bR8OZ/cjH4eEN9QdbAYBsavEsJ6wxBDgdweUH+7fQfORn5/IJtqwGha +FAyfeTwRI8/8tA4J1U08/p8oIopbzcGTg1LjPHQ8s8RTtknzg+jTrWkCAwEAAaNT +MFEwHQYDVR0OBBYEFKkx59QcInNCTOluvzHt3frciFqtMB8GA1UdIwQYMBaAFKkx +59QcInNCTOluvzHt3frciFqtMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAKyPiDT0PXTHzpC7sLq7s2pUe3ezqg51m4NTgynO3u+vYftTCMVDIV0P +H7ZJNyHHYvglaLjRnsWdjGs4tcqjIYCRL8Tnc/CVorI0QGGwDDlvWXNXleAxjV3t +t5hy7t5JpnNgF2GYNnSeuYG21rDDcP9EQZKv1a2KaKcgFRtzktlegRX0kGjDxbYe +0jo5j4oJZ944W79GqcQLeRpOU8PnbSaBtRPSeX1VhzpkR3pQy5FgBikQkvRP/dJw +H5Ck1P7fRzDnlv4cypSk6v9PWSQMrSYlyp1a40b9U1bIF2cQ+HVDwUg+56OPAey6 +wBELObfROXagGdjRP+OQJUJgFHS3Uss= +-----END CERTIFICATE----- diff --git a/tests/data/notary_service_container/signer/config.json b/tests/data/notary_service_container/signer/config.json new file mode 100644 index 000000000..dfe7bc2b6 --- /dev/null +++ b/tests/data/notary_service_container/signer/config.json @@ -0,0 +1,11 @@ +{ + "server": { + "grpc_addr": ":7899", + "tls_cert_file": "./notary-signer.crt", + "tls_key_file": "./notary-signer.key", + "client_ca_file": "./ca.crt" + }, + "storage": { + "backend": "memory" + } + } diff --git a/tests/data/notary_service_container/signer/notary-signer.crt b/tests/data/notary_service_container/signer/notary-signer.crt new file mode 100644 index 000000000..e8eb44b5f --- /dev/null +++ b/tests/data/notary_service_container/signer/notary-signer.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdKgAwIBAgIURG6fK3XvIY0hybWB9UUl016hmOIwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOTm90YXJ5IFJvb3QgQ0EwHhcNMjMxMTIyMjIxMzM2WhcN +MjQxMTIxMjIxMzM2WjAYMRYwFAYDVQQDDA1ub3Rhcnkuc2lnbmVyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnpgKhkqK2+R2ln3OFPOmOzt/19aoMYVR +gibtecfXzysFNTeF/OBIq9vO6oP1aYrKNfednZWs3VupQQn0cQ0tN3eAtA6Gl6bh +BToAQnh5ii8WLsi8gg1K5Plvyd2oCYBGxx3Z2FYPMtd6iJ2kjAo+DoL4/LdxgbaX +x3b0blYn0fyo2CUy8mLS3Swnuk1j1YpRaYKMtTRpq3OOrrLaCasOHY037aCQ5G4W +JeIFuLFsvp/Ym0PiwQhh9kuPLfjwZts7tDTyXIq6GrrlNWm3q0cVUDZ8Rqca0EL2 +EgstMACzMh+3mx0uW9LVWnib1kYprwLt/ghIFQoJvCf63BfiRZdZwwIDAQABoysw +KTAnBgNVHREEIDAegg1ub3Rhcnktc2lnbmVygg1ub3Rhcnkuc2lnbmVyMA0GCSqG +SIb3DQEBCwUAA4IBAQC0SGmLJJVOCYqSd3GV5JKEIZhu6hIf9MWbd6OsOJt/lX33 +QmhjiV+yL/uZKANyMCDqtpQwKKhr28i2pHhD+5ThikxkPD82cYsAc4SkuotUT08f +CZU/Tn4621Ft23ANtV/tOrU2b6vBjJCmHH2OJHF+T69Jc6x5RqNthWyqRa9WAW4R +kWui4POWRpkddI4UAr7wCnWW7PFBPSlN8tHOanRG2ry+DQYAqK6HzxdLfItQ4s4v +DgQPpXyaG7XsaEs7y/DjhX//GQg/VUceYvbs5UX+eV0c5IGeklWemzKlqDdHh5Oo +0JPPsuLayS3EKJoikAmJzVtRNt49aEyv+2tEy+Aj +-----END CERTIFICATE----- diff --git a/tests/data/notary_service_container/signer/notary-signer.key b/tests/data/notary_service_container/signer/notary-signer.key new file mode 100644 index 000000000..d5c1879b3 --- /dev/null +++ b/tests/data/notary_service_container/signer/notary-signer.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCemAqGSorb5HaW +fc4U86Y7O3/X1qgxhVGCJu15x9fPKwU1N4X84Eir287qg/Vpiso1952dlazdW6lB +CfRxDS03d4C0DoaXpuEFOgBCeHmKLxYuyLyCDUrk+W/J3agJgEbHHdnYVg8y13qI +naSMCj4Ogvj8t3GBtpfHdvRuVifR/KjYJTLyYtLdLCe6TWPVilFpgoy1NGmrc46u +stoJqw4djTftoJDkbhYl4gW4sWy+n9ibQ+LBCGH2S48t+PBm2zu0NPJciroauuU1 +aberRxVQNnxGpxrQQvYSCy0wALMyH7ebHS5b0tVaeJvWRimvAu3+CEgVCgm8J/rc +F+JFl1nDAgMBAAECggEAI0SQYpjFFG1T6deEMqiUOOvcXDVCJfEN1TGu8bv9Q/a/ +K7xzMW72+jDhbMl3k8bnfOTZyid0z60IkIwULefOPF1445GvYJ4dwDnLwPxlmtMp +zbGaKhmmpBDMSUnfim1aUAFVZoSM0LOWbjcQVowYjMWXdSueNEhUrGjJppfGNy3s +8XLZIhmENNlsJsgbdgszZ8qPwK6npmeafmMc2lS44hxwr7nj0Y082QohXnLhcqoh +gmJ9aSpAD6Nsavp2kdZ1bF0AqJNppIHCCeg9toMmpgjMKggW6/Y1BHpXP+ONB7zd +cdnggfWbPSZYoAI4bLWLvUcyW3yqey7QKqubPyN+8QKBgQDSsDwZhL2fDfXSyUhZ +fhmczFNwA2NsEzyhVGHtS5MB05hWr6XQedtCnldPDXHSJwsKK98faAi8YQIxzBWc +pwcmc5wDgLeu2M0uJCxgMCecQOmZ3W1zkzagmYIwtkuP3rXPCJG2kcTMAGwDwZTO +zgSRY9qmCoae71BoAU0L9xD1XwKBgQDAs6phUy4jSplZQk7zrevZe/Nct3cemWMS +NJefLtZC5y4PJ8060pz43nBbq1l2T0n5ZC/S/K4fCH2IjSdrSCdtRrzjqMcYd92C +lmGjhsVyXTITcH4tXjErTTHUs/nWk7STiu7q6qeJxxFyNAzLJDMn5CXXHLF5fNNw +imqdWKEyHQKBgQCpOH+tB16+B5pv6NdBefTcaYiCqVYLkg/ajEnzLAYxK2BqD74g +ih8/jKoXhnbrEgzd6IrXUNnjZA89K+wX3Ffz3FtsvM/LkbqK2ucBguvtpn155c1p +TM5Ng757nY9nSLvCQ+G1P7NPHu+ivLLmv7YPiKIvRrkForV0M8dMWu62BQKBgAqF +RJYProMqvXiMEDdplWjIRZ8YPR6kjS4fRO/h5Ly+VltpduDxQrSroELA9h9pcMZ2 +282PEgqLsh7UZgSLaeujYwii3EvPr35Dq4z7/KejwuogyCK9871Dd6b/NHKsmb08 +ZpLYwNDa127+vHwSu+A/qnk2DdJuKDUKuYthnVtZAoGBALtfB7eC6nDWOm2mv20h +r7sp4wUlhBEliN0vg+0YQaV2qVeMJ3uXTZBSSKftdKElfQCXbt7i2bDwNCwK5UdC ++kqS2ISwtUcgF4dyCi5HO59nvqv5uHZRZalHVnkT/1t6XSM1wSvi7AURWZW+wEQ7 +mIjnfjw9jNfMAxxAjlGEcYVv +-----END PRIVATE KEY----- diff --git a/tests/integration/cases.yaml b/tests/integration/cases.yaml index 62f1cf2b5..006bb1927 100644 --- a/tests/integration/cases.yaml +++ b/tests/integration/cases.yaml @@ -77,6 +77,23 @@ test_cases: namespace: default expected_msg: Defaulting debug container name to debugger- expected_result: VALID + self-hosted-notary: + # "shnu" is exactly the same test case as "ru" from the regular test + # to ensure the new settings did not render connaisseur dysfunctional + - id: shnu + txt: Testing unsigned image... + type: deploy + ref: securesystemsengineering/testimage:unsigned + namespace: default + expected_msg: Unable to find signed digest for image docker.io/securesystemsengineering/testimage:unsigned. + expected_result: INVALID + - id: shnsi + txt: Testing signed image with trust data in self-hosted notary... + type: deploy + ref: securesystemsengineering/testimage:self-hosted-notary-signed + namespace: default + expected_msg: pod/pod-shnsi-${RAND} created + expected_result: VALID cosign: - id: cu txt: Testing unsigned cosign image... diff --git a/tests/integration/integration-test.sh b/tests/integration/integration-test.sh index f6d30cfd4..ad9bb64d0 100755 --- a/tests/integration/integration-test.sh +++ b/tests/integration/integration-test.sh @@ -357,12 +357,33 @@ update_helm_for_workloads() { rm update } +update_for_self_hosted_notary() { + envsubst update + yq '. *+ load("ghcr-values")' -i update + yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' helm/values.yaml update + rm update + curl "https://notary.server:4443/v2/docker.io/securesystemsengineering/testimage/_trust/tuf/root.json" > root.json + SELF_HOSTED_NOTARY_PUBLIC_ROOT_KEY_ID=$(cat root.json | jq -r .signatures[0].keyid) + cat root.json | jq -r --arg KEYID $SELF_HOSTED_NOTARY_PUBLIC_ROOT_KEY_ID '.signed.keys | .[$KEYID] | .keyval.public' | base64 -d | openssl x509 -pubkey -noout > self_hosted_notary_root_key.pub + yq -i '(.application.validators.[] | select(.name == "self-hosted-notary") | .trustRoots.[] | select(.name=="default") | .key) |= load_str("self_hosted_notary_root_key.pub")' helm/values.yaml + rm self_hosted_notary_root_key.pub root.json +} + enable_alerting() { envsubst update yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' helm/values.yaml update rm update } +render_notary_host() { + envsubst update_deployment_with_host_alias.yaml + # It is not possible to yq the deployment.yaml as it's not valid yaml due to the helm variables, thus the awk magic here + HOST_ALIAS=$(cat update_deployment_with_host_alias.yaml) + awk -v host_alias="${HOST_ALIAS}" '/ spec:/ { print; print host_alias; next }1' helm/templates/deployment.yaml > deployment.yaml + mv deployment.yaml helm/templates/ + rm update_deployment_with_host_alias.yaml +} + debug_values() { #PATH echo "::group::values.yaml" yq '... comments=""' "$1" @@ -410,6 +431,11 @@ regular_int_test() { rm diff.log } +### RUN SELF-HOSTED NOTARY INTEGRATION TEST #################################### +self_hosted_notary_int_test() { + multi_test "self-hosted-notary" +} + ### COSIGN TEST #################################### cosign_int_test() { multi_test "cosign" @@ -553,6 +579,12 @@ case $1 in helm_repo_install pre_config_int_test ;; +"self-hosted-notary") + update_for_self_hosted_notary + render_notary_host + make_install + self_hosted_notary_int_test + ;; "complexity") update_values_minimal update_values '.kubernetes.deployment.replicasCount=3' '.kubernetes.deployment.resources= {"limits": {"cpu":"1000m", "memory":"512Mi"},"requests": {"cpu":"500m", "memory":"512Mi"}}' diff --git a/tests/integration/notary_addhash.sh b/tests/integration/notary_addhash.sh new file mode 100755 index 000000000..f2e232ba5 --- /dev/null +++ b/tests/integration/notary_addhash.sh @@ -0,0 +1,8 @@ +#!/usr/bin/expect + +spawn notary addhash docker.io/securesystemsengineering/testimage self-hosted-notary-signed 528 --sha256 [lindex $argv 0] --publish -c ./tests/data/notary_service_container/config/client_config.json -s https://notary.server:4443 -D +expect "Enter passphrase for targets key with ID*\r" +send -- "0123456789\r" +expect "Enter passphrase for snapshot key with ID*\r" +send -- "0123456789\r" +expect eof diff --git a/tests/integration/notary_init.sh b/tests/integration/notary_init.sh new file mode 100755 index 000000000..8dfaf2e19 --- /dev/null +++ b/tests/integration/notary_init.sh @@ -0,0 +1,16 @@ +#!/usr/bin/expect + +spawn notary init docker.io/securesystemsengineering/testimage --publish -c ./tests/data/notary_service_container/config/client_config.json -s https://notary.server:4443 -D +expect "Enter passphrase for new root key with ID*\r" +send -- "0123456789\r" +expect "Repeat passphrase for new root key with ID*\r" +send -- "0123456789\r" +expect "Enter passphrase for new targets key with ID*\r" +send -- "0123456789\r" +expect "Repeat passphrase for new targets key with ID*\r" +send -- "0123456789\r" +expect "Enter passphrase for new snapshot key with ID*\r" +send -- "0123456789\r" +expect "Repeat passphrase for new snapshot key with ID*\r" +send -- "0123456789\r" +expect eof diff --git a/tests/integration/update-self-hosted-notary.yaml b/tests/integration/update-self-hosted-notary.yaml new file mode 100644 index 000000000..6df5ed882 --- /dev/null +++ b/tests/integration/update-self-hosted-notary.yaml @@ -0,0 +1,66 @@ +kubernetes: + deployment: + replicasCount: 1 +application: + validators: + - name: allow + type: static + approve: true + - name: deny + type: static + approve: false + - name: dockerhub-basics + type: notaryv1 + host: notary.docker.io + trustRoots: + - name: docker-official + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOXYta5TgdCwXTCnLU09W5T4M4r9f + QQrqJuADP6U7g5r9ICgPSmZuRHP/1AYUfOQW3baveKsT969EfELKj1lfCA== + -----END PUBLIC KEY----- + - name: securesystemsengineering-official + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsx28WV7BsQfnHF1kZmpdCTTLJaWe + d0CA+JOi8H4REuBaWSZ5zPDe468WuOJ6f71E7WFg3CVEVYHuoZt2UYbN/Q== + -----END PUBLIC KEY----- + - name: self-hosted-notary + type: notaryv1 + host: notary.server:4443 + trustRoots: + - name: default + key: self_hosted_notary_root_key.pub + # CA cert signing the cert of the self-hosted notary + # (tests/data/notary_service_container/certs/root/ca.crt) + cert: | + -----BEGIN CERTIFICATE----- + MIIDEzCCAfugAwIBAgIUDDvwp5mG7ckbcvJ0jg9he/rSGzAwDQYJKoZIhvcNAQEL + BQAwGTEXMBUGA1UEAwwOTm90YXJ5IFJvb3QgQ0EwHhcNMjMxMTIyMjIwOTM5WhcN + MjQxMTIxMjIwOTM5WjAZMRcwFQYDVQQDDA5Ob3RhcnkgUm9vdCBDQTCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6IjUiNqWDrwO1Yn/Yfc5hSUdH3ZZ1i + SzbsYL4ZL3ZN/rXv8rS3JBeNpbcRs85lYIjC8prCh9tJ6FjnMqUVI5pGz9XZPLNO + hQA4eW4Z5lHDd1oWCT1Y66zwUYSD59xUzGWCZ5OhZlKanu8OfdGDyoPJxKYmhZQz + Vnnh1chZC5eBBzObufWO47aMg4rX7ZLCER/HxLHfSnCVMVwOa3KBHDK3yWy+y86y + 7/0bl5iW/bR8OZ/cjH4eEN9QdbAYBsavEsJ6wxBDgdweUH+7fQfORn5/IJtqwGha + FAyfeTwRI8/8tA4J1U08/p8oIopbzcGTg1LjPHQ8s8RTtknzg+jTrWkCAwEAAaNT + MFEwHQYDVR0OBBYEFKkx59QcInNCTOluvzHt3frciFqtMB8GA1UdIwQYMBaAFKkx + 59QcInNCTOluvzHt3frciFqtMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAKyPiDT0PXTHzpC7sLq7s2pUe3ezqg51m4NTgynO3u+vYftTCMVDIV0P + H7ZJNyHHYvglaLjRnsWdjGs4tcqjIYCRL8Tnc/CVorI0QGGwDDlvWXNXleAxjV3t + t5hy7t5JpnNgF2GYNnSeuYG21rDDcP9EQZKv1a2KaKcgFRtzktlegRX0kGjDxbYe + 0jo5j4oJZ944W79GqcQLeRpOU8PnbSaBtRPSeX1VhzpkR3pQy5FgBikQkvRP/dJw + H5Ck1P7fRzDnlv4cypSk6v9PWSQMrSYlyp1a40b9U1bIF2cQ+HVDwUg+56OPAey6 + wBELObfROXagGdjRP+OQJUJgFHS3Uss= + -----END CERTIFICATE----- + policy: + - pattern: "*:*" + validator: dockerhub-basics + with: + trustRoot: securesystemsengineering-official + - pattern: "k8s.gcr.io/*:*" + validator: allow + - pattern: docker.io/securesystemsengineering/connaisseur:* + validator: allow + - pattern: "docker.io/securesystemsengineering/testimage:self-hosted-notary-signed" + validator: self-hosted-notary diff --git a/tests/integration/update_deployment_notary_test.yaml b/tests/integration/update_deployment_notary_test.yaml new file mode 100644 index 000000000..412e31b8c --- /dev/null +++ b/tests/integration/update_deployment_notary_test.yaml @@ -0,0 +1,4 @@ + hostAliases: + - ip: ${NOTARY_IP} + hostnames: + - "notary.server" From b2cd4a6a7ac142e7d5c25a768e35b9ce2a562ede Mon Sep 17 00:00:00 2001 From: Anneke Breust <44376590+annekebr@users.noreply.github.com> Date: Thu, 23 Nov 2023 12:56:23 +0100 Subject: [PATCH 4/4] update: add k8s version 1.28 for integration tests --- .github/workflows/.reusable-integration-test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml index 715eb7d87..e07bdf040 100644 --- a/.github/workflows/.reusable-integration-test.yml +++ b/.github/workflows/.reusable-integration-test.yml @@ -139,7 +139,7 @@ jobs: - uses: ./.github/actions/k8s-version-config name: Setup k8s cluster with: - k8s-version: v1.25 + k8s-version: v1.28 - name: Get alerting endpoint IP id: get_alerting_endpoint_ip uses: ./.github/actions/alerting-endpoint @@ -216,7 +216,7 @@ jobs: - uses: ./.github/actions/k8s-version-config name: Setup k8s cluster with: - k8s-version: v1.25 + k8s-version: v1.28 - name: Run test run: | bash tests/integration/integration-test.sh "${{ matrix.integration-test-arg }}" @@ -261,6 +261,7 @@ jobs: "v1.25", "v1.26", "v1.27", + "v1.28", ] steps: - name: Checkout code