Skip to content

Commit

Permalink
Enable signing using a custom sigstore instance
Browse files Browse the repository at this point in the history
This PR extends plugin schema to allow users to specify a custom,
ie, non-Public-Good Sigstore Instance to use, including the TUF URLs
(used to initialise cosign).

Sensible defaults use the Public-Good Sigstore Instance with their
current URLs (known to cosign) and the buildkite-agent as the OIDC
provider.

Documentation was amended to emphasise usage of image digest vs tag
and to add more examples for both keyless and keyed signing using
the Public-Good and a custom sigstore instance.

Bonus:
- fix BUILDKITE_PLUGIN_COSIGN variables names
- remove the need to explicitly state the Public-Good Sigstore Instance
default params (ie, the URLs) - those might change and cosign would know them
- refactor code to group and then reuse the common logic, increase readability
- bump cosign default version to 2.2.4
- bump plugin-tester to version 4.1.1
- use specific plugin-linter version 2.1.0 instead of latest
- fix tests/pre-checkout.bats to work with v2.1.0
  • Loading branch information
prezha committed Jun 18, 2024
1 parent 04e3e24 commit f278f7b
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 93 deletions.
1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ jobs:
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true

6 changes: 2 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
BUILDKITE_TESTER_IMAGE=buildkite/plugin-tester:v3.0.1
BUILDKITE_TESTER_IMAGE=buildkite/plugin-tester:v4.1.1

# NOTE(jaosorior): This hasn't been released in two years...
# we should ask for a fix.
BUILDKITE_LINTER_IMAGE=buildkite/plugin-linter:latest
BUILDKITE_LINTER_IMAGE=buildkite/plugin-linter:v2.1.0

PLUGIN_REF=equinixmetal-buildkite/cosign

Expand Down
131 changes: 107 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,101 @@
# cosign buildkite plugin
# cosign Buildkite plugin

The cosign buildkite plugin provides a convenient mechanism for running the
open-source cosign container signing tool for your containers. For more information
about cosign, please refer to their
The cosign Buildkite plugin provides a convenient mechanism for running the
open-source cosign OCI container image signing tool for your containers.
For more information about cosign, please refer to their
[documentation](https://docs.sigstore.dev/cosign/overview).

**Important notes**

To ensure you know what you're signing:

- It's best to have this plugin run as part of the image CI build step (where the
built image is stored locally) and not as a separate step (signing a remote image).
- It's strongly recommended to use image digest instead of image tag (plugin will
automatically try to infer and use digest based on the provided image tag).
Otherwise, you might get a warning from cosign, or it may even refuse to sign the image:
>WARNING: Image reference ghcr.io/my-project/my-image:v1.2.3 uses a tag, not a
digest, to identify the image to sign.
This can lead you to sign a different image than the intended one. Please use a
digest (example.com/ubuntu@sha256:abc123...) rather than tag
(example.com/ubuntu:latest) for the input to cosign. The ability to refer to
images by tag will be removed in a future release.

## Features

- Automatically downloads and verifies the cosign executable if it cannot be
- Automatically downloads and verifies the `cosign` executable if it cannot be
found in the `PATH` environment variable's directories

## Basic signing example
## Basic signing examples

The following code snippets demonstrates how to use the plugin in a pipeline
step with the configuration parameters and upload the signature to the same
repository as the container image.

### Keyless signing

#### Using the Public-Good Sigstore Instance

```yml
steps:
- plugins:
- equinixmetal-buildkite/cosign#v0.1.0:
image: "ghcr.io/my-project/my-image@sha256:1e1e4f97dd84970160975922715909577d6c12eaaf6047021875674fa7166c27"
```
The following code snippet demonstrates how to use the plugin in a pipeline
step with the default plugin configuration parameters:
#### Using a custom Sigstore Instance
```yml
steps:
- command: ls
plugins:
- plugins:
- equinixmetal-buildkite/cosign#v0.1.0:
image: "ghcr.io/my-project/my-image:latest"
keyless: true
image: "ghcr.io/my-project/my-image@sha256:1e1e4f97dd84970160975922715909577d6c12eaaf6047021875674fa7166c27"
keyless-config:
fulcio-url: "https://fulcio.sigstore.dev"
rekor-url: "https://rekor.sigstore.dev"
tuf-mirror-url: "https://tuf.my-sigstore.dev"
tuf-root-url: "https://tuf.my-sigstore.dev/root.json"
rekor-url: "https://rekor.my-sigstore.dev"
fulcio-url: "https://fulcio.my-sigstore.dev"
```
This will use keyless signatures and upload the signature to the same repository
as the image. Note that if the Fulcio URL and Rekor URL are not specified, the
plugin will use the default values presented.
### Keyed signing
Note: Currently, only the file-based keyed signing is supported.
#### Using the Public-Good Sigstore Instance
```yml
steps:
- plugins:
- equinixmetal-buildkite/cosign#v0.1.0:
image: "ghcr.io/my-project/my-image@sha256:1e1e4f97dd84970160975922715909577d6c12eaaf6047021875674fa7166c27"
keyless: false
keyed-config:
key: "/path-to/cosign.key"
```
#### Using a custom Sigstore Instance
```yml
steps:
- plugins:
- equinixmetal-buildkite/cosign#v0.1.0:
image: "ghcr.io/my-project/my-image@sha256:1e1e4f97dd84970160975922715909577d6c12eaaf6047021875674fa7166c27"
keyless: false
keyed-config:
tuf-mirror-url: "https://tuf.my-sigstore.dev"
tuf-root-url: "https://tuf.my-sigstore.dev/root.json"
rekor-url: "https://rekor.my-sigstore.dev"
key: "/path-to/cosign.key"
```
## Configuration
### `image` (Required, string)

References the image to sign
References the image to sign.

To avoid issues, use the image digest instead of image tag.
See `Important notes` above for details.

### `keyless` (Optional, boolean)

Expand All @@ -45,18 +105,41 @@ plugin will use a keypair. If not specified, the plugin will default to `true`.
### `keyless-config` (Optional, object)

If `keyless` is set to `true`, the plugin will use the following configuration
parameters to sign the image:

- `fulcio_url` (Optional, string): The URL of the Fulcio server to use. If not
specified, the plugin will default to `https://fulcio.sigstore.dev`.
- `rekor_url` (Optional, string): The URL of the Rekor server to use. If not
specified, the plugin will default to `https://rekor.sigstore.dev`.
parameters to sign the container image:

- `tuf-mirror-url` (Optional, string):
The URL of the TUF server to use. If not specified, the plugin will use
the default TUF URL of the Public-Good Sigstore Instance.
- `tuf-root-url` (Optional, string):
The URL of the TUF root JSON file to use. If not specified, the plugin will use
the default TUF root JSON file URL of the Public-Good Sigstore Instance.
- `rekor_url` (Optional, string):
The URL of the Rekor server to use. If not specified, the plugin will use
the default Rekor URL of the Public-Good Sigstore Instance.
- `fulcio_url` (Optional, string):
The URL of the Fulcio server to use. If not specified, the plugin will use
the default Fulcio URL of the Public-Good Sigstore Instance.
- `oidc-issuer` (Optional, string):
The URL of the OIDC issuer. If not specified, the plugin will use
the default OIDC issuer URL of the Public-Good Sigstore Instance.
- `oidc-provider` (Optional, string):
The URL of the OIDC provider. If not specified, the plugin will use
the default `buildkite-agent` OIDC provider for Buildkite.

### `keyed-config` (Optional, object)

If `keyless` is set to `false`, the plugin will use the following configuration
parameters to sign the image:

- `tuf-mirror-url` (Optional, string):
The URL of the TUF server to use. If not specified, the plugin will use
the default TUF URL of the Public-Good Sigstore Instance.
- `tuf-root-url` (Optional, string):
The URL of the TUF root JSON file to use. If not specified, the plugin will use
the default TUF root JSON file URL of the Public-Good Sigstore Instance.
- `rekor_url` (Optional, string):
The URL of the Rekor server to use. If not specified, the plugin will use
the default Rekor URL of the Public-Good Sigstore Instance.
- `key` (Required, string): The path to the private key to use.

### `cosign-version` (Optional, string)
Expand Down
162 changes: 112 additions & 50 deletions hooks/post-command
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -33,90 +33,145 @@ display_success() {
buildkite-agent annotate --style success "$message<br />" --context "$ctx"
}

# if the supplied image reference does not contain a digest,
# try getting the local image digest to use it instead, and
# if that fails, warn then continue using the supplied image reference
use_image_digest() {
if [[ $image != *"@sha256:"* ]]; then
echo "--- :docker: Getting the local image digest for ${image}"

local digest
digest=$(docker inspect --format='{{index .RepoDigests 0}}' "${image}")

status=$?
if [[ $status -ne 0 ]]; then
display_error "docker inspect" "Failed to get the local image digest, will continue using supplied image reference ${image}"
else
display_success "docker inspect" "Will continue using ${digest}"
image="${digest}"
fi
fi
}

# Parameters
############
# Common parameters
###################

# This is a required parameter
# image is a required parameter
image=${BUILDKITE_PLUGIN_COSIGN_IMAGE}
if [[ -z "${image}" ]]; then
fail_with_message "cosign" "No image specified"
fail_with_message "cosign" "Image not specified"
fi
use_image_digest

# flags for the cosign sign command
sign_flags=("-y" "--output-signature" "out.sig")

is_keyless=${BUILDKITE_PLUGIN_COSIGN_KEYLESS:-true}

# Hook functions
################

cosign_keyless() {
local fulcio_url=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_FULCIO_URL:-"https://fulcio.sigstore.dev"}
local rekor_url=${BUILDKITE_PLUGIN_COSIG_KEYLESS_CONFIGN_REKOR_URL:-"https://rekor.sigstore.dev"}
local oidc_issuer=${BUILDKITE_PLUGIN_COSIG_KEYLESS_CONFIGN_OIDC_ISSUER:-"https://oauth2.sigstore.dev/auth"}
local oidc_provider=${BUILDKITE_PLUGIN_COSIG_KEYLESS_CONFIGN_OIDC_PROVIDER:-"buildkite-agent"}

echo "--- :key: Cosign keyless signing"

rm out.sig || true

COSIGN_EXPERIMENTAL=1 cosign sign \
-y \
--fulcio-url="${fulcio_url}" \
--rekor-url="${rekor_url}" \
--oidc-issuer="${oidc_issuer}" \
--oidc-provider="${oidc_provider}" \
--output-signature=out.sig \
"${image}"
# if provided, initialise cosign with a custom TUF configuration
cosign_init() {
echo "--- :key: Init cosign"

status=$?
if [[ $status -ne 0 ]]; then
fail_with_message "cosign" "Failed to sign image"
# flags for the cosign initialize command
init_flags=()

if [[ "${is_keyless}" == true ]]; then
local tuf_mirror_url=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_TUF_MIRROR_URL}
local tuf_root_url=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_TUF_ROOT_URL}
else
local tuf_mirror_url=${BUILDKITE_PLUGIN_COSIGN_KEYED_CONFIG_TUF_MIRROR_URL}
local tuf_root_url=${BUILDKITE_PLUGIN_COSIGN_KEYED_CONFIG_TUF_ROOT_URL}
fi

local signature=$(cat out.sig)
if [[ -n "${tuf_mirror_url}" ]]; then
init_flags+=("--mirror" "${tuf_mirror_url}")
fi

display_success "cosign" "Successfully signed image."
cat <<EOF | buildkite-agent annotate --style success --context "cosign-signature"
### Signed image
\`\`\`
$image
\`\`\`
if [[ -n "${tuf_root_url}" ]]; then
init_flags+=("--root" "${tuf_root_url}")
fi

### Signature
\`\`\`
$signature
\`\`\`
EOF
if [ ${#init_flags[@]} -gt 0 ]; then
rm -rf ~/.sigstore

cosign initialize "${init_flags[@]}"

rm out.sig || true
status=$?
if [[ $status -ne 0 ]]; then
fail_with_message "cosign" "Failed to initialise"
fi
display_success "cosign" "Successfully initialised"
else
display_success "cosign" "Initialisation not required, skipping"
fi
}

setup_keyless() {
echo "--- :key: Setup cosign keyless signing"

local rekor_url=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_REKOR_URL}
if [[ -n "${rekor_url}" ]]; then
sign_flags+=("--rekor-url" "${rekor_url}")
fi

local fulcio_url=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_FULCIO_URL}
if [[ -n "${fulcio_url}" ]]; then
sign_flags+=("--fulcio-url" "${fulcio_url}")
fi

local oidc_issuer=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_OIDC_ISSUER}
if [[ -n "${oidc_issuer}" ]]; then
sign_flags+=("--oidc-issuer" "${oidc_issuer}")
fi

local oidc_provider=${BUILDKITE_PLUGIN_COSIGN_KEYLESS_CONFIG_OIDC_PROVIDER:-"buildkite-agent"}
if [[ -n "${oidc_provider}" ]]; then
sign_flags+=("--oidc-provider" "${oidc_provider}")
fi
}

cosign_keyed() {
echo "--- :key: Cosign keyed signing"
setup_keyed() {
echo "--- :key: Setup cosign keyed signing"

local rekor_url=${BUILDKITE_PLUGIN_COSIGN_KEYED_CONFIG_REKOR_URL}
if [[ -n "${rekor_url}" ]]; then
sign_flags+=("--rekor-url" "${rekor_url}")
fi

local key=${BUILDKITE_PLUGIN_COSIGN_KEYED_CONFIG_KEY:-}
if [[ -z "${key}" ]]; then
fail_with_message "cosign" "Key not specified"
fi

if [[ ! -f "${key}" ]]; then
fail_with_message "cosign" "Key file not found in path ${key}"
fi

rm out.sig || true
sign_flags+=("--key" "${key}")
}

# sign the image
cosign_sign() {
echo "--- :key: Signing image with cosign"

rm -f out.sig

cosign sign \
-y \
--key="${key}" \
--output-signature=out.sig \
"${sign_flags[@]}" \
"${image}"

status=$?
if [[ $status -ne 0 ]]; then
fail_with_message "cosign" "Failed to sign image"
fi

local signature=$(cat out.sig)
local signature
signature=$(cat out.sig)

display_success "cosign" "Successfully signed image."
display_success "cosign" "Successfully signed image"
cat <<EOF | buildkite-agent annotate --style success --context "cosign-signature"
### Signed image
\`\`\`
Expand All @@ -129,11 +184,18 @@ $signature
\`\`\`
EOF

rm out.sig || true
rm -f out.sig
}

if [[ "${is_keyless}" == "true" ]]; then
cosign_keyless
# Main
#######

cosign_init

if [[ "${is_keyless}" == true ]]; then
setup_keyless
else
cosign_keyed
setup_keyed
fi

cosign_sign
Loading

0 comments on commit f278f7b

Please sign in to comment.