Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/redundant network calls to notary during auth #1376

Merged
merged 4 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/alerting-endpoint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 19 additions & 0 deletions .github/actions/notary-server-ip/action.yml
Original file line number Diff line number Diff line change
@@ -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})
annekebr marked this conversation as resolved.
Show resolved Hide resolved
echo notary_ip=${NOTARY_IP} >> ${GITHUB_OUTPUT}
shell: bash
19 changes: 19 additions & 0 deletions .github/actions/notary-signer-ip/action.yaml
Original file line number Diff line number Diff line change
@@ -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})
annekebr marked this conversation as resolved.
Show resolved Hide resolved
echo notary_signer_ip=${NOTARY_IP} >> ${GITHUB_OUTPUT}
shell: bash
32 changes: 32 additions & 0 deletions .github/actions/setup-notary/action.yaml
Original file line number Diff line number Diff line change
@@ -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
annekebr marked this conversation as resolved.
Show resolved Hide resolved
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
86 changes: 81 additions & 5 deletions .github/workflows/.reusable-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down Expand Up @@ -139,15 +139,15 @@ 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_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: |
Expand All @@ -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.28
- 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
Expand All @@ -186,6 +261,7 @@ jobs:
"v1.25",
"v1.26",
"v1.27",
"v1.28",
]
steps:
- name: Checkout code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
82 changes: 58 additions & 24 deletions connaisseur/validators/notaryv1/notary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
annekebr marked this conversation as resolved.
Show resolved Hide resolved
)
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(
Expand All @@ -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
Expand Down Expand Up @@ -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}."
Expand All @@ -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."
Expand All @@ -246,12 +271,21 @@ 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,
validation_kind="Authentication token",
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}
Loading
Loading