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

Added support for container identity for cosign #270

Merged
merged 2 commits into from
Jun 27, 2024
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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ kerberos
marshmallow
urllib3<2
pubtools-sign>0.0.6
docker
2 changes: 1 addition & 1 deletion src/pubtools/_quay/clear_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def clear_repositories(repositories: str, settings: Dict[str, Any]) -> None:
LOG.info("Clearing repositories '{0}'".format(repositories))
quay_client = QuayClient(settings["quay_user"], settings["quay_password"])
item_processor = item_processor_for_internal_data(
quay_client, "quay.io", 5, settings["quay_org"]
quay_client, "quay.io", [""], 5, settings["quay_org"]
)
item_processor.extractor.full_extract = True

Expand Down
7 changes: 7 additions & 0 deletions src/pubtools/_quay/iib_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ def _index_image_to_sign_entries(
reference
"""
iib_repo = target_settings["quay_operator_repository"]
pub_iib_repo = target_settings["quay_operator_repository"]
dest_registries = target_settings["docker_settings"]["docker_reference_registry"]
pub_registry = dest_registries[0]
dest_registries = dest_registries if isinstance(dest_registries, list) else [dest_registries]
if internal:
dest_registries = ["quay.io"]
Expand Down Expand Up @@ -130,6 +132,7 @@ def _index_image_to_sign_entries(
to_sign_entries.append(
SignEntry(
reference=f"{registry}/{iib_repo}:{_dest_tag}",
pub_reference=f"{pub_registry}/{pub_iib_repo}",
repo=iib_repo,
digest=headers["docker-content-digest"],
arch="amd64",
Expand All @@ -145,6 +148,7 @@ def _index_image_to_sign_entries(
to_sign_entries.append(
SignEntry(
reference=reference,
pub_reference=f"{pub_registry}/{pub_iib_repo}",
repo=iib_repo,
digest=digest,
arch="amd64",
Expand Down Expand Up @@ -267,6 +271,7 @@ def task_iib_add_bundles(
item_processor = item_processor_for_internal_data(
quay_client,
target_settings["quay_host"].rstrip("/"),
target_settings["docker_settings"]["docker_reference_registry"],
target_settings.get("retry_sleep_time", 5),
quay_operator_namespace,
)
Expand Down Expand Up @@ -401,6 +406,7 @@ def task_iib_remove_operators(
item_processor = item_processor_for_internal_data(
quay_client,
target_settings["quay_host"].rstrip("/"),
target_settings["docker_settings"]["docker_reference_registry"],
target_settings.get("retry_sleep_time", 5),
quay_operator_namespace,
)
Expand Down Expand Up @@ -530,6 +536,7 @@ def task_iib_build_from_scratch(
item_processor = item_processor_for_internal_data(
quay_client,
target_settings["quay_host"].rstrip("/"),
target_settings["docker_settings"]["docker_reference_registry"],
target_settings.get("retry_sleep_time", 5),
quay_operator_namespace,
)
Expand Down
13 changes: 12 additions & 1 deletion src/pubtools/_quay/item_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ class SignEntry:
signing_key (str): Signing key.
repo (str): Repo reference in format <registry>/<repo>
reference (str): Reference in format <registry>/<repo>:<tag>
pub_reference (str): Public repo reference in format <registry>/repo
digest (str): Digest of the manifest.
arch (str): Architecture of the manifest.
"""

repo: str
reference: str
pub_reference: str
digest: str
signing_key: str
arch: str
Expand Down Expand Up @@ -393,6 +395,7 @@ class ItemProcesor:
reference_processor: Union[ReferenceProcessorExternal, ReferenceProcessorInternal]
reference_registries: List[str]
source_registry: Optional[str]
public_registry: Optional[str]

INTERNAL_DELIMITER = "----"

Expand Down Expand Up @@ -505,6 +508,7 @@ def generate_to_sign(self, item: Any, include_manifest_lists: bool = False) -> L
SignEntry(
repo=repo,
reference=reference,
pub_reference=cast(str, self.public_registry) + "/" + repo,
digest=mad.digest,
arch=mad.arch,
signing_key=item.claims_signing_key,
Expand Down Expand Up @@ -700,17 +704,23 @@ def item_processor_for_external_data(
reference_processor=ReferenceProcessorExternal(),
reference_registries=external_registries,
source_registry=None,
public_registry=external_registries[0],
)


def item_processor_for_internal_data(
quay_client: QuayClient, internal_registry: str, retry_sleep_time: int, internal_namespace: str
quay_client: QuayClient,
internal_registry: str,
external_registries: List[str],
retry_sleep_time: int,
internal_namespace: str,
) -> ItemProcesor:
"""Get instance of item processor configured to produce internal data.

Args:
quay_client (QuayClient): Quay client.
internal_registry (str): Docker registry where containers are stored
external registries (str): List of external registries used for container identity.
retry_sleep_time (int): sleep time bewteen retries for fetching data from registry.
internal_namespace (str): Namespace of internal organization in the registry.
Returns:
Expand All @@ -723,4 +733,5 @@ def item_processor_for_internal_data(
reference_processor=reference_processor,
reference_registries=["quay.io"],
source_registry=internal_registry,
public_registry=external_registries[0],
)
7 changes: 7 additions & 0 deletions src/pubtools/_quay/push_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ def generate_backup_mapping(
internal_item_processor = item_processor_for_internal_data(
self.dest_quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries,
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
)
Expand Down Expand Up @@ -503,6 +504,7 @@ def fetch_missing_push_items_digests(
item_processor = item_processor_for_internal_data(
self.dest_quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries,
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
)
Expand Down Expand Up @@ -557,10 +559,13 @@ def sign_new_manifests(self, docker_push_items: List[Any]) -> List[Tuple[str, st
+ ":"
+ tag
)
registry = self.dest_registries[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an image is pulled from registry.access.redhat.com, identity in signature starts with registry.redhat.io, would signature verification fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure how validation works. But when doing validation in podman for example it uses containers/policy.json where you specify trusted host. SO if you have there both hosts, it should work

pub_reference = f"{registry}/{repo}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The critical.identity.docker-reference field should follow the same rules / design as for the GPG signatures:
For every tag the image is published under, there should be a signature with identity set to exactly that tag.

Does tag need to be included in identity?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cosign doesn't include it in docker-reference. I guess it's because tag is option when you sign with cosign

# add entries in internal format for cosign
to_sign_entries_internal.append(
SignEntry(
reference=reference,
pub_reference=pub_reference,
repo=repo,
digest=digest,
signing_key=key,
Expand All @@ -572,6 +577,7 @@ def sign_new_manifests(self, docker_push_items: List[Any]) -> List[Tuple[str, st
to_sign_entries.append(
SignEntry(
reference=reference,
pub_reference="",
repo=repo,
digest=digest,
signing_key=key,
Expand Down Expand Up @@ -666,6 +672,7 @@ def run(self) -> None:
to_sign_entries = []
item_processor = item_processor_for_internal_data(
self.src_quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries,
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
Expand Down
2 changes: 1 addition & 1 deletion src/pubtools/_quay/remove_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def remove_repositories(repositories: str, settings: Dict[str, Any]) -> None:
quay_api_client = QuayApiClient(settings["quay_api_token"])
quay_client = QuayClient(settings["quay_user"], settings["quay_password"], "quay.io")
item_processor = item_processor_for_internal_data(
quay_client, "quay.io", 5, settings["quay_org"]
quay_client, "quay.io", [""], 5, settings["quay_org"]
)
item_processor.extractor.full_extract = True
# Remove repository doesn't work with push item by default, therefore we create VirtualPushItem
Expand Down
24 changes: 19 additions & 5 deletions src/pubtools/_quay/signer_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ def _store_signed(self, signatures: Dict[str, Any]) -> None:
self._run_store_signed(signatures)

def sign_container_opt_args(
self, sign_entry: SignEntry, task_id: Optional[str] = None
self, sign_entry: List[SignEntry], task_id: Optional[str] = None
) -> Dict[str, Any]:
"""Return optional arguments for signing a container.

Args:
sign_entry (SignEntry): SignEntry to sign.
sign_entries (List[SignEntry]): List of SignEntry.
task_id (str): Task ID to identify the signing task if needed.

Returns:
Expand Down Expand Up @@ -140,7 +140,7 @@ def _sign_containers(
if not sign_entries:
return
sign_entry = sign_entries[0]
opt_args = self.sign_container_opt_args(sign_entry, task_id)
opt_args = self.sign_container_opt_args(sign_entries, task_id)
signed = self.entry_point(
config_file=self.config_file,
signing_key=sign_entry.signing_key,
Expand Down Expand Up @@ -225,12 +225,12 @@ def _filter_to_sign(self, to_sign_entries: List[SignEntry]) -> List[SignEntry]:
return ret

def sign_container_opt_args(
self, sign_entry: SignEntry, task_id: Optional[str] = None
self, sign_entries: List[SignEntry], task_id: Optional[str] = None
) -> Dict[str, Any]:
"""Return optional arguments for signing a container.

Args:
sign_entry (SignEntry): SignEntry to sign.
sign_entries (List[SignEntry]): List of SignEntry.
task_id (str): Task ID to identify the signing task if needed.

Returns:
Expand Down Expand Up @@ -528,6 +528,20 @@ class CosignSignerWrapper(SignerWrapper):
# LOG.warning("Fetch existing signatures error:" + esig[1])
# return rets

def sign_container_opt_args(
self, sign_entries: List[SignEntry], task_id: Optional[str] = None
) -> Dict[str, Any]:
"""Return optional arguments for signing a container.

Args:
sign_entries (List[SignEntry]): List of SignEntry.
task_id (str): Task ID to identify the signing task if needed.

Returns:
dict: Optional arguments for signing a container.
"""
return {"identity": [sign_entry.pub_reference for sign_entry in sign_entries]}

def _filter_to_remove(
self,
signatures: List[Tuple[str, str, str]],
Expand Down
12 changes: 11 additions & 1 deletion src/pubtools/_quay/tag_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ def copy_tag_sign_images(self, push_item: Any, tag: str, executor: Executor) ->
to_sign_entries_internal.append(
SignEntry(
repo=repo,
pub_reference=registries[0] + "/" + list(push_item.repos.keys())[0],
reference="quay.io/"
+ self.target_settings["quay_namespace"]
+ "/"
Expand All @@ -566,6 +567,7 @@ def copy_tag_sign_images(self, push_item: Any, tag: str, executor: Executor) ->
to_sign_entries.append(
SignEntry(
repo=repo,
pub_reference="",
reference=reference,
digest=details.digest,
signing_key=push_item.claims_signing_key,
Expand All @@ -577,6 +579,7 @@ def copy_tag_sign_images(self, push_item: Any, tag: str, executor: Executor) ->
item_processor = item_processor_for_internal_data(
self.quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries[0],
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
)
Expand Down Expand Up @@ -666,9 +669,11 @@ def merge_manifest_lists_sign_images(
to_sign_entries_internal = []

for manifest in new_manifest_list["manifests"]:
registry = dest_registries[0]
to_sign_entries_internal.append(
SignEntry(
repo=list(push_item.repos.keys())[0],
pub_reference=registry + "/" + list(push_item.repos.keys())[0],
reference="quay.io/"
+ self.target_settings["quay_namespace"]
+ "/"
Expand All @@ -687,6 +692,7 @@ def merge_manifest_lists_sign_images(
to_sign_entries.append(
SignEntry(
repo=list(push_item.repos.keys())[0],
pub_reference="",
reference=reference,
digest=manifest["digest"],
arch=manifest["platform"]["architecture"],
Expand All @@ -701,6 +707,7 @@ def merge_manifest_lists_sign_images(
item_processor = item_processor_for_internal_data(
self.quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries[0],
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
)
Expand Down Expand Up @@ -747,13 +754,15 @@ def merge_manifest_lists_sign_images(
else:
ml_to_sign = json.dumps(new_manifest_list)
self.quay_client.upload_manifest(new_manifest_list, dest_image)
print(ml_to_sign)

if push_item.claims_signing_key:
# for cosign sign also manifest list
pub_registry = dest_registries[0]
pub_reference = pub_registry + "/" + list(push_item.repos.keys())[0]
to_sign_entries_internal.append(
SignEntry(
repo=list(push_item.repos.keys())[0],
pub_reference=pub_reference,
reference="quay.io/"
+ self.target_settings["quay_namespace"]
+ "/"
Expand Down Expand Up @@ -818,6 +827,7 @@ def untag_image(self, push_item: Any, tag: str) -> None:
item_processor = item_processor_for_internal_data(
self.quay_client,
self.target_settings["quay_host"].rstrip("/"),
self.dest_registries[0],
self.target_settings.get("retry_sleep_time", 5),
self.target_settings["quay_namespace"],
)
Expand Down
Loading