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

Document the expected Sigstore CLI interface #32

Merged
merged 49 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
995fa1d
Remove Docker infrastructure
tetsuo-cpp Oct 12, 2022
4c38424
Create initial `sigstore-conformance` GitHub Action
tetsuo-cpp Oct 12, 2022
b3a935c
Code documentation
tetsuo-cpp Oct 12, 2022
5247870
Fix test comment
tetsuo-cpp Oct 12, 2022
3b6d976
Add variable for debug logging
tetsuo-cpp Oct 12, 2022
7809f26
Add linting to CI
tetsuo-cpp Oct 12, 2022
c3b1089
Add selftest to conformance test `sigstore-python`
tetsuo-cpp Oct 12, 2022
38f1700
Make `action.py` executable
tetsuo-cpp Oct 12, 2022
3a584af
Check in missing init file
tetsuo-cpp Oct 12, 2022
c4c7ddb
Rename selftest job
tetsuo-cpp Oct 12, 2022
e9a4448
Use mypy and fix errors
tetsuo-cpp Oct 12, 2022
9caae33
Make mypy ignore the pytest import
tetsuo-cpp Oct 12, 2022
183091f
Reword entrypoint documentation
tetsuo-cpp Oct 12, 2022
dfdbf65
Add README
tetsuo-cpp Oct 12, 2022
4ef46b0
Update README.md
tetsuo-cpp Oct 12, 2022
9150abd
Move token permissions into selftest job
tetsuo-cpp Oct 13, 2022
2adf983
Merge branch 'alex/test-action' into alex/readme
tetsuo-cpp Oct 13, 2022
84b4013
Reformat README and adjust permissions guidance to be more restrictive
tetsuo-cpp Oct 13, 2022
d164700
Add initial version of CLI protocol
tetsuo-cpp Oct 13, 2022
1676e19
Make guarantee more explicit
tetsuo-cpp Oct 13, 2022
66c71f9
Reword initial paragraph
tetsuo-cpp Oct 13, 2022
b1e2be4
Update docs/cli_protocol.md
tetsuo-cpp Oct 13, 2022
be79400
Update docs/cli_protocol.md
tetsuo-cpp Oct 13, 2022
c5da57d
Update docs/cli_protocol.md
tetsuo-cpp Oct 13, 2022
8706f85
Make it clear that the entrypoint is a substitution
tetsuo-cpp Oct 13, 2022
3e5667e
Use metavars in option lists
tetsuo-cpp Oct 13, 2022
0013f76
Merge remote-tracking branch 'trailofbits/main' into alex/cli-protocol
tetsuo-cpp Nov 2, 2022
d743958
Update tests to use CLI protocol
tetsuo-cpp Nov 2, 2022
c6ac271
Don't verify SAN. There doesn't seem to be a way to programatically
tetsuo-cpp Nov 2, 2022
b34b010
Remove certificate email from CLI protocol
tetsuo-cpp Nov 2, 2022
c9e082e
Use CLI protocol flag for OIDC issuer
tetsuo-cpp Nov 2, 2022
279d937
Use conformance wrapper for self test
tetsuo-cpp Nov 2, 2022
be83e04
Fix arg replacement in conformance wrapper
tetsuo-cpp Nov 2, 2022
89aa6ea
Add workspace path into entrypoint instead of modifying path
tetsuo-cpp Nov 2, 2022
5f315f7
Variable not substituting for some reason
tetsuo-cpp Nov 2, 2022
c389d0c
Try getting the syntax right
tetsuo-cpp Nov 2, 2022
acff5a4
Debugging
tetsuo-cpp Nov 2, 2022
5f76b78
Debug logging
tetsuo-cpp Nov 2, 2022
3ecccb2
Debug in the test
tetsuo-cpp Nov 2, 2022
93c053d
Convert exit code
tetsuo-cpp Nov 2, 2022
a452cdb
Fix executable name in conformance wrapper
tetsuo-cpp Nov 2, 2022
ac64a54
Remove subcommand descriptions
tetsuo-cpp Nov 2, 2022
fd6cee9
Support SAN verification
tetsuo-cpp Nov 2, 2022
ab2e353
Got the mappings the wrong way around
tetsuo-cpp Nov 2, 2022
a051cec
Add URI prefix to expected SAN
tetsuo-cpp Nov 3, 2022
89ae8b6
Print signing cert for debugging
tetsuo-cpp Nov 3, 2022
a99fcb0
Remove URI prefix remove debugging code
tetsuo-cpp Nov 3, 2022
fcd7c5a
Fix comment
tetsuo-cpp Nov 3, 2022
917fe97
Add note in README to explain the required workflow path
tetsuo-cpp Nov 3, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
uses: ./
id: sigstore-conformance
with:
entrypoint: sigstore
entrypoint: ${{ github.workspace }}/sigstore-python-conformance
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ALL_PY_SRCS := action.py \
sigstore-python-conformance \
$(shell find test/ -name '*.py')

.PHONY: all
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ sigstore-conformance

## Usage

Simply add `trailofbits/sigstore-conformance` to one of your workflows.
Simply create a new workflow file at `.github/workflows/conformance.yml` and add
the `trailofbits/sigstore-conformance` action to it.

```yaml
jobs:
Expand All @@ -35,6 +36,10 @@ In the example above, the workflow is installing [sigstore-python](https://githu
and providing `sigstore` as the `entrypoint` since this is the command used to
invoke the client.

The workflow that uses this action **must** be at
`.github/workflows/conformance.yml`. This is a current limitation of the test
suite and is required to reliably verify signing certificates.

The relevant job must have permission to request the OIDC token to authenticate
with. This can be done by adding a `permission` setting within the job that
invokes the `trailofbits/sigstore-conformance` action.
Expand Down
55 changes: 55 additions & 0 deletions docs/cli_protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Conformance CLI Protocol
========================

While all Sigstore clients share some core functionality, each of them have
their own unique features and idiosyncracies that make their command lines
incompatible with one another. `sigstore-conformance` aims to test this common
feature set.

As an example of incompatibilities between clients, [cosign's](https://github.com/sigstore/cosign)
`sign` subcommand is designed to sign Docker containers rather than arbitrary
files (this behaviour is accessed via cosign's `sign-blob` subcommand). This
makes sense for cosign's use case, however it does diverge from other Sigstore
clients such as [sigstore-python](https://github.com/sigstore/sigstore-python)
and [sigstore-js](https://github.com/sigstore/sigstore-js).

To resolve these differences, we've established a CLI protocol for the test
suite to uniformly communicate with clients under test. Since the client's CLI
is unlikely to conform to this protocol, it may be necessary to write a thin
wrapper that converts the options described in this document to those that the
client's native CLI accepts.

## Subcommands

This is the set of subcommands that the test CLI must support. Each subcommand
has a provided syntax and list of descriptions for each argument.

To simplify argument parsing, all arguments are required and will **always** be
supplied by the conformance suite in the order that they are specified in the
templates below.

### Sign

```console
${ENTRYPOINT} sign --signature FILE --certificate FILE FILE
```

| Option | Description |
| --- | --- |
| `--signature FILE` | The path to write the signature to |
| `--certificate FILE` | The path to write the signing certificate to |
| `FILE` | The artifact to sign |

### Verify

```console
${ENTRYPOINT} verify --signature FILE --certificate FILE --certificate-oidc-issuer URL FILE
```

| Option | Description |
| --- | --- |
| `--signature FILE` | The path to the signature to verify |
| `--certificate FILE` | The path to the signing certificate to verify |
| `--certificate-email EMAIL` | The The expected email in the signing certificate's SAN extension |
| `--certificate-oidc-issuer URL` | The expected OIDC issuer for the signing certificate |
| `FILE` | The path to the artifact to verify |
26 changes: 26 additions & 0 deletions sigstore-python-conformance
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3

"""
A wrapper to convert `sigstore-conformance` CLI protocol invocations to match `sigstore-python`.
"""

import subprocess
import sys

ARG_REPLACEMENTS = {
"--certificate-email": "--cert-email",
"--certificate-oidc-issuer": "--cert-oidc-issuer",
}

# Trim the script name.
fixed_args = sys.argv[1:]

# Replace incompatible flags.
fixed_args = [
ARG_REPLACEMENTS[arg] if arg in ARG_REPLACEMENTS else arg for arg in fixed_args
]

# Prepend the `sigstore-python` executable name.
fixed_args = ["sigstore"] + fixed_args

subprocess.run(fixed_args, text=True, check=True)
32 changes: 26 additions & 6 deletions test/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import os
import subprocess

CERTIFICATE_EMAIL = (
os.environ["GITHUB_SERVER_URL"]
+ "/"
+ os.environ["GITHUB_REPOSITORY"]
+ "/.github/workflows/conformance.yml@"
+ os.environ["GITHUB_REF"]
)
CERTIFICATE_OIDC_ISSUER = "https://token.actions.githubusercontent.com"


class SigstoreClient:
"""
Expand Down Expand Up @@ -32,29 +41,40 @@ def run(self, *args) -> None:
)

def sign(
self, artifact: os.PathLike, certificate: os.PathLike, signature: os.PathLike
self, artifact: os.PathLike, signature: os.PathLike, certificate: os.PathLike
) -> None:
"""
Sign an artifact with the Sigstore client.

`artifact` is a path to the file to sign.
`certificate` is the path to write the signing certificate to.
`signature` is the path to write the generated signature to.
`certificate` is the path to write the signing certificate to.
"""
self.run(
"sign", "--certificate", certificate, "--signature", signature, artifact
"sign", "--signature", signature, "--certificate", certificate, artifact
)

def verify(
self, artifact: os.PathLike, certificate: os.PathLike, signature: os.PathLike
self, artifact: os.PathLike, signature: os.PathLike, certificate: os.PathLike
) -> None:
"""
Verify an artifact with the Sigstore client.

`artifact` is the path to the file to verify.
`certificate` is the path to the signing certificate to verify with.
`signature` is the path to the signature to verify.
`certificate` is the path to the signing certificate to verify with.
"""
# The email and OIDC issuer cannot be specified by the test since they remain constant
# across the GitHub Actions job.
self.run(
"verify", "--certificate", certificate, "--signature", signature, artifact
"verify",
"--signature",
signature,
"--certificate",
certificate,
"--certificate-email",
CERTIFICATE_EMAIL,
"--certificate-oidc-issuer",
CERTIFICATE_OIDC_ISSUER,
artifact,
)
12 changes: 6 additions & 6 deletions test/test_sign_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ def test_sign_verify(client: SigstoreClient) -> None:
client.
"""
artifact_path = Path("artifact.txt")
certificate_path = Path("artifact.txt.crt")
signature_path = Path("artifact.txt.sig")
certificate_path = Path("artifact.txt.crt")

assert artifact_path.exists()
assert not certificate_path.exists()
assert not signature_path.exists()
assert not certificate_path.exists()

# Sign the artifact.
client.sign(artifact_path, certificate_path, signature_path)
client.sign(artifact_path, signature_path, certificate_path)

assert certificate_path.exists()
assert signature_path.exists()
assert certificate_path.exists()

# Verify the artifact signature
client.verify(artifact_path, certificate_path, signature_path)
# Verify the artifact signature.
client.verify(artifact_path, signature_path, certificate_path)