Skip to content
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: 2 additions & 0 deletions labs/lab8/keys/cosign.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5ktDSD78DFA0jzMFZGXYl+KWS4WX
bB8f69nGZiO7GcWpyeQjox1YpFTm7lc8hT3W72FT/ff8fls/ZVlzBtn88w==
359 changes: 359 additions & 0 deletions submissions/lab8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
# Lab 8 — Submission

## Task 1: Sign + Tamper Demo

### Registry and image push

- Registry container name: `lab8-registry`, listening on `localhost:5000`
- Base registry image: `registry:3`
- Pushed image: `localhost:5000/juice-shop:v20.0.0`
- Digest of the pushed image:

```text
localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe
```
The digest was obtained from the local registry API after the image was pushed. Because Docker sent a single-platform manifest to the local registry, the digest differs from the multi-platform manifest on Docker Hub.

## Signing

The image was signed by its digest using the generated Cosign private key:

```text
Signing: localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe
Pushing signature to: localhost:5000/juice-shop
The private key lives at labs/lab8/keys/cosign.key and is excluded via the repository's *.key gitignore rule. Only the public key at labs/lab8/keys/cosign.pub is committed.
```

## Verification (PASSED)

Command:

```bash
cosign verify \
--key labs/lab8/keys/cosign.pub \
--allow-insecure-registry \
--insecure-ignore-tlog \
"localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe"
```
Output:

```text
WARNING: Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the signature.

Verification for localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
```
Verified claims:

```json
[
{
"critical": {
"identity": {
"docker-reference": "localhost:5000/juice-shop"
},
"image": {
"docker-manifest-digest": "sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe"
},
"type": "cosign container image signature"
},
"optional": null
}
]
```
Exit code:

```text
0
```

## Tamper demo (FAILED – correct behaviour)

A different image (alpine:3.20) was pushed under a tag that looks similar:

```text
localhost:5000/juice-shop:v20.0.0-tampered
```
Original digest:

```text
localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe
```
Tampered (Alpine) digest:

```text
localhost:5000/juice-shop@sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e
```
Result of cosign verify on the tampered digest:

```text
WARNING: Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the signature.
Error: no signatures found
error during command execution: no signatures found
```
Exit code:

```text
10
```
The failure is expected because no Cosign signature exists for the Alpine manifest digest.

## Sanity check – original signature still valid

After pushing the tampered image, the original digest was verified again:

```text
Verification for localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"localhost:5000/juice-shop"},"image":{"docker-manifest-digest":"sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe"},"type":"cosign container image signature"},"optional":null}]
```
Exit code:

```text
0
```
### Importance of digest binding

An OCI tag is merely a mutable label that can be reassigned to a different manifest while the repository name stays the same. Cosign signs the fixed manifest digest, so replacing the image or retagging changes the digest and breaks signature verification.

If Cosign signed only the tag name, an attacker could redirect the signed-looking tag to a malicious image, preserving the human-readable reference. Digest binding prevents such substitution because the signature is valid only for the exact bytes represented by the original digest.

## Task 2: SBOM + Provenance Attestations

### SBOM attestation
```
Attached: yes
Attestation type: cyclonedx
Command exit code: 0
Source SBOM file: labs/lab4/juice-shop.cdx.json
Source SBOM format: CycloneDX 1.6
```
Command:

```bash
cosign attest \
--key labs/lab8/keys/cosign.key \
--type cyclonedx \
--predicate labs/lab4/juice-shop.cdx.json \
--allow-insecure-registry \
--tlog-upload=false \
--yes \
"$DIGEST"
```
Output:

```text
Using payload from: labs/lab4/juice-shop.cdx.json
```
Decoded payload excerpt from verify-attestation:
```
json
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://cyclonedx.org/bom",
"subject": [
{
"name": "localhost:5000/juice-shop",
"digest": {
"sha256": "28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe"
}
}
],
"predicate": {
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"components": [
{
"author": "Benjamin Byholm <bbyholm@abo.fi> (https://github.com/kkoopa/), Mathias Küsel (https://github.com/mathiask88/)",
"bom-ref": "pkg:npm/1to2@1.0.0?package-id=3cea2309a653e6ed",
"cpe": "cpe:2.3:a:nodejs:1to2:1.0.0:*:*:*:*:*:*:*",
"description": "NAN 1 -> 2 Migration Script",
"externalReferences": [
{
"type": "distribution",
"url": "git://github.com/nodejs/nan.git"
}
],
"licenses": [
{
"license": {
"id": "MIT"
}
}
]
}
]
}
}
```
Component counts:

```text
Source: 1846
Extracted: 1846
Component count matches Lab 4 source: yes
```

Diff command:

```bash
diff \
<(jq -S '.components | length' labs/lab4/juice-shop.cdx.json) \
<(jq -S '.components | length' labs/lab8/results/sbom-from-attestation.json)
```
Diff output was empty, exit code 0, confirming identical component counts.

## Provenance attestation
```
Attached: yes
Attestation type: slsaprovenance
Command exit code: 0
Builder ID: https://localhost/lab8-student
Build type: https://example.com/lab8/local-build
Source repository: https://github.com/m1d0rfeed/DevSecOps-Intro
Source commit: f9932471eb75787a0588c96ee2fc4f65ed0772e8
```
Predicate body:
```
json
{
"builder": {
"id": "https://localhost/lab8-student"
},
"buildType": "https://example.com/lab8/local-build",
"invocation": {
"configSource": {
"uri": "https://github.com/m1d0rfeed/DevSecOps-Intro",
"digest": {
"sha1": "f9932471eb75787a0588c96ee2fc4f65ed0772e8"
}
}
}
}
```
Verification output:

```text
Verification for localhost:5000/juice-shop@sha256:28870b9d2bec49e605d6ebbf4b22ed1ec1ca0a72347ef19217bbbb21ea44e3fe --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
```
Decoded provenance predicate:
```
json
{
"builder": {
"id": "https://localhost/lab8-student"
},
"buildType": "https://example.com/lab8/local-build",
"invocation": {
"configSource": {
"uri": "https://github.com/m1d0rfeed/DevSecOps-Intro",
"digest": {
"sha1": "f9932471eb75787a0588c96ee2fc4f65ed0772e8"
}
}
}
}
```
### Value for a future verifier (Lab 9)

A valid image signature confirms that the image hasn’t been altered since signing, but it doesn’t describe what software is inside. An attached, verified SBOM provides a signed inventory that an admission controller or security workflow can check against vulnerability databases and policy rules.

When a new vulnerability like Log4Shell is disclosed, an unsigned image or one without an SBOM would still need rescanning or manual investigation to decide whether the affected library is present. With a signed and verified SBOM, the vulnerable component and version can be matched immediately against the attested inventory. A Kyverno verifyImages policy can then require both the signature and the expected attestation predicate before allowing admission.

## Bonus: Blob Signing (Codecov 2021 mitigation)

### Release artifact

A sample installer was packaged into a tarball:

```bash
cat > /tmp/install.sh <<'EOF'
#!/bin/bash
echo "Welcome to my-cool-tool installer"
echo "Running setup..."
EOF

chmod +x /tmp/install.sh
tar -czf labs/lab8/results/my-tool.tar.gz -C /tmp install.sh
```
Archive contents:

```text
install.sh
```
### Sign and verify

Signed files:

```text
my-tool.tar.gz
my-tool.tar.gz.bundle
```
Signing command:

```bash
cosign sign-blob \
--key labs/lab8/keys/cosign.key \
--yes \
--tlog-upload=false \
--bundle labs/lab8/results/my-tool.tar.gz.bundle \
labs/lab8/results/my-tool.tar.gz
```
Signing output:

```text
Using payload from: labs/lab8/results/my-tool.tar.gz
Wrote bundle to file labs/lab8/results/my-tool.tar.gz.bundle
```
Verification command:

```bash
cosign verify-blob \
--key cosign.pub \
--bundle my-tool.tar.gz.bundle \
--insecure-ignore-tlog \
my-tool.tar.gz
```
Successful verification output:

```text
WARNING: Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the blob.
Verified OK
```
Exit code:

```text
0
```
### Tamper test failed (as expected)

The archive was modified after signing:

```bash
echo "MALICIOUS PAYLOAD" >> my-tool.tar.gz
```
Verification result:

```text
WARNING: Skipping tlog verification is an insecure practice that lacks of transparency and auditability verification for the blob.
Error: invalid signature when validating ASN.1 encoded signature
error during command execution: invalid signature when validating ASN.1 encoded signature
```
Exit code:

```text
1
```
## Mitigation of the Codecov 2021 incident

The Codecov Bash uploader was consumed by CI pipelines without any cryptographic check, so a modified uploader could execute while still being fetched from a trusted location. If users had obtained the uploader together with a trusted Cosign public key and a signature bundle, they could have run cosign verify-blob before executing the script.

A tampered uploader would no longer match the signed byte stream, generating an invalid-signature error like the one above. The CI job could then abort before invoking bash, preventing the malicious script from accessing environment variables, credentials, and other secrets available to the build process.