Skip to content

Commit

Permalink
feat: implement cosign_sign (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
thesayyn authored Jan 2, 2023
1 parent 835d197 commit a3bf6e7
Show file tree
Hide file tree
Showing 20 changed files with 506 additions and 16 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ gazelle_binary(
languages = ["@bazel_skylib//gazelle/bzl"],
)

# gazelle:exclude example/**
gazelle(
name = "gazelle",
gazelle = "gazelle_bin",
Expand Down
4 changes: 4 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ oci_register_toolchains(
zot_version = LATEST_ZOT_VERSION,
)

load("//cosign:repositories.bzl", "cosign_register_toolchains")

cosign_register_toolchains(name = "oci_cosign")

# For running our own unit tests
load("@bazel_skylib//lib:unittest.bzl", "register_unittest_toolchains")

Expand Down
29 changes: 29 additions & 0 deletions cosign/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

toolchain_type(
name = "toolchain_type",
visibility = ["//visibility:public"],
)

bzl_library(
name = "defs",
srcs = ["defs.bzl"],
visibility = ["//visibility:public"],
deps = ["//cosign/private:sign"],
)

bzl_library(
name = "repositories",
srcs = ["repositories.bzl"],
visibility = ["//visibility:public"],
deps = [
"//cosign/private:versions",
"//oci/private:toolchains_repo",
],
)

bzl_library(
name = "toolchain",
srcs = ["toolchain.bzl"],
visibility = ["//visibility:public"],
)
5 changes: 5 additions & 0 deletions cosign/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"Public API"

load("//cosign/private:sign.bzl", _cosign_sign = "cosign_sign")

cosign_sign = _cosign_sign
25 changes: 25 additions & 0 deletions cosign/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

exports_files(
glob(["*.bzl"]),
visibility = ["//docs:__pkg__"],
)

exports_files([
"sign.sh.tpl",
])

bzl_library(
name = "sign",
srcs = ["sign.bzl"],
visibility = [
"//cosign:__subpackages__",
"//docs:__pkg__",
],
)

bzl_library(
name = "versions",
srcs = ["versions.bzl"],
visibility = ["//cosign:__subpackages__"],
)
77 changes: 77 additions & 0 deletions cosign/private/sign.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"Implementation details for sign rule"

_DOC = """Sign an oci_image using cosign binary at a remote registry.
It signs the image by its digest determined beforehand.
```starlark
oci_image(
name = "image"
)
cosign_sign(
name = "sign",
image = ":image",
repository = "index.docker.io/org/image"
)
```
`repository` attribute can be overridden using the `--repository` flag.
```starlark
oci_image(
name = "image"
)
cosign_sign(
name = "sign",
image = ":image",
repository = "index.docker.io/org/image"
)
```
run `bazel run :sign -- --repository=index.docker.io/org/test`
"""

_attrs = {
"image": attr.label(allow_single_file = True, doc = "Label to an oci_image"),
"repository": attr.string(mandatory = True, doc = "Repository URL where the image will be signed at. eg: index.docker.io/<user>/image. digests and tags are disallowed."),
"_sign_sh_tpl": attr.label(default = "sign.sh.tpl", allow_single_file = True),
}

def _cosign_sign_impl(ctx):
cosign = ctx.toolchains["@contrib_rules_oci//cosign:toolchain_type"]
yq = ctx.toolchains["@aspect_bazel_lib//lib:yq_toolchain_type"]

if ctx.attr.repository.find(":") != -1 or ctx.attr.repository.find("@") != -1:
fail("repository attribute should not contain digest or tag.")

executable = ctx.actions.declare_file("cosign_sign_{}.sh".format(ctx.label.name))
ctx.actions.expand_template(
template = ctx.file._sign_sh_tpl,
output = executable,
is_executable = True,
substitutions = {
"{{cosign_path}}": cosign.cosign_info.binary.short_path,
"{{yq_path}}": yq.yqinfo.bin.short_path,
"{{image_dir}}": ctx.file.image.short_path,
"{{fixed_args}}": " ".join(["--repository", ctx.attr.repository]),
},
)

runfiles = ctx.runfiles(files = [ctx.file.image])
runfiles = runfiles.merge(yq.default.default_runfiles)
runfiles = runfiles.merge(cosign.default.default_runfiles)

return DefaultInfo(executable = executable, runfiles = runfiles)

cosign_sign = rule(
implementation = _cosign_sign_impl,
attrs = _attrs,
doc = _DOC,
executable = True,
toolchains = [
"@contrib_rules_oci//cosign:toolchain_type",
"@aspect_bazel_lib//lib:yq_toolchain_type",
],
)
26 changes: 26 additions & 0 deletions cosign/private/sign.sh.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -o pipefail -o errexit -o nounset

readonly COSIGN="{{cosign_path}}"
readonly YQ="{{yq_path}}"
readonly IMAGE_DIR="{{image_dir}}"
readonly DIGEST=$("${YQ}" '.manifests[].digest' "${IMAGE_DIR}/index.json")
readonly FIXED_ARGS=({{fixed_args}})

# set $@ to be FIXED_ARGS+$@
ARGS=(${FIXED_ARGS[@]} $@)
set -- ${ARGS[@]}

REPOSITORY=""
ARGS=()

while (( $# > 0 )); do
case "$1" in
--repository) shift; REPOSITORY="$1"; shift ;;
(--repository=*) REPOSITORY="${1#--repository=}"; shift ;;
*) ARGS+=( "$1" ); shift ;;
esac
done

exec "${COSIGN}" sign "${REPOSITORY}@${DIGEST}" ${ARGS[@]+"${ARGS[@]}"}

13 changes: 13 additions & 0 deletions cosign/private/versions.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


COSIGN_VERSIONS = {
"v1.6.0": {
"darwin-amd64": "sha256-/P8XqU+4pQmMm5tiPi4ZDMTTxHxPXo2/dbcqVqh0shk=",
"darwin-arm64": "sha256-5Z+0mjzAOtu4Hb0vXNYgb+CUec27dCbN0bIqr5FFu7w=",
"linux-amd64": "sha256-tirIwasc2wctRC0vPbfX/+l3VmphcM0D3UjkWD2tMgM=",
"linux-arm": "sha256-y2+8Kaq6iWMLoJgWjCIMmZI/e9OCElOwb6dyiHSNdR8=",
"linux-arm64": "sha256-XxyLsrMMdfsccsJmsI2c/FF924tjLjVif9Y6rwno8b0=",
"linux-ppc64le": "sha256-Do6Fc7PUfxpAhvocpDTjpq1LZUs8AIaYIQYnmH7gPMw=",
"linux-s390x": "sha256-53+ZFZljoXRuvYGoGaxfgb2oJyUGCqesr8PbTXRnb68="
}
}
67 changes: 67 additions & 0 deletions cosign/repositories.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Repository rules for fetching cosign"""

load("//cosign/private:versions.bzl", "COSIGN_VERSIONS")

# buildifier: disable=bzl-visibility
load("//oci/private:toolchains_repo.bzl", "PLATFORMS", "toolchains_repo")

COSIGN_BUILD_TMPL = """\
# Generated by container/repositories.bzl
load("@contrib_rules_oci//cosign:toolchain.bzl", "cosign_toolchain")
cosign_toolchain(
name = "cosign_toolchain",
cosign = "cosign"
)
"""

def _cosign_repo_impl(repository_ctx):
platform = repository_ctx.attr.platform.replace("x86_64", "amd64").replace("_", "-")
url = "https://github.com/sigstore/cosign/releases/download/{version}/cosign-{platform}".format(
version = repository_ctx.attr.cosign_version,
platform = platform,
)
repository_ctx.download(
url = url,
output = "cosign",
executable = True,
integrity = COSIGN_VERSIONS[repository_ctx.attr.cosign_version][platform],
)
repository_ctx.file("BUILD.bazel", COSIGN_BUILD_TMPL)

cosign_repositories = repository_rule(
_cosign_repo_impl,
doc = "Fetch external tools needed for cosign toolchain",
attrs = {
"cosign_version": attr.string(mandatory = True, values = COSIGN_VERSIONS.keys()),
"platform": attr.string(mandatory = True, values = PLATFORMS.keys()),
},
)

# Wrapper macro around everything above, this is the primary API
def cosign_register_toolchains(name):
"""Convenience macro for users which does typical setup.
- create a repository for each built-in platform like "cosign_linux_amd64" -
this repository is lazily fetched when node is needed for that platform.
- create a repository exposing toolchains for each platform like "oci_platforms"
- register a toolchain pointing at each platform
Users can avoid this macro and do these steps themselves, if they want more control.
Args:
name: base name for cosign repository, like "oci_cosign"
"""
toolchain_name = "{name}_toolchains".format(name = name)

for platform in PLATFORMS.keys():
cosign_repositories(
name = "{name}_{platform}".format(name = name, platform = platform),
platform = platform,
cosign_version = COSIGN_VERSIONS.keys()[0],
)
native.register_toolchains("@{}//:{}_toolchain".format(toolchain_name, platform))

toolchains_repo(
name = toolchain_name,
toolchain_type = "@contrib_rules_oci//cosign:toolchain_type",
# avoiding use of .format since {platform} is formatted by toolchains_repo for each platform.
toolchain = "@%s_{platform}//:cosign_toolchain" % name,
)
54 changes: 54 additions & 0 deletions cosign/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""This module implements the cosign-specific toolchain rule."""

CosignInfo = provider(
doc = "Information about how to invoke the cosign executable.",
fields = {
"binary": "Executable cosign binary",
},
)

def _cosign_toolchain_impl(ctx):
binary = ctx.executable.cosign

# Make the $(COSIGN_BIN) variable available in places like genrules.
# See https://docs.bazel.build/versions/main/be/make-variables.html#custom_variables
template_variables = platform_common.TemplateVariableInfo({
"COSIGN_BIN": binary.path,
})
default = DefaultInfo(
files = depset([binary]),
runfiles = ctx.runfiles(files = [binary]),
)
cosign_info = CosignInfo(
binary = binary,
)

# Export all the providers inside our ToolchainInfo
# so the resolved_toolchain rule can grab and re-export them.
toolchain_info = platform_common.ToolchainInfo(
cosign_info = cosign_info,
template_variables = template_variables,
default = default,
)
return [
default,
toolchain_info,
template_variables,
]

cosign_toolchain = rule(
implementation = _cosign_toolchain_impl,
attrs = {
"cosign": attr.label(
doc = "A hermetically downloaded cosign executable target for the target platform.",
mandatory = True,
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
doc = """Defines a cosign toolchain.
For usage see https://docs.bazel.build/versions/main/toolchains.html#defining-toolchains.
""",
)
5 changes: 5 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ stardoc_with_diff_test(
bzl_library_target = "//oci/private:image_index",
)

stardoc_with_diff_test(
name = "cosign_sign",
bzl_library_target = "//cosign/private:sign",
)

update_docs(name = "update")
55 changes: 55 additions & 0 deletions docs/cosign_sign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->

Implementation details for sign rule

<a id="#cosign_sign"></a>

## cosign_sign

<pre>
cosign_sign(<a href="#cosign_sign-name">name</a>, <a href="#cosign_sign-image">image</a>, <a href="#cosign_sign-repository">repository</a>)
</pre>

Sign an oci_image using cosign binary at a remote registry.

It signs the image by its digest determined beforehand.

```starlark
oci_image(
name = "image"
)

cosign_sign(
name = "sign",
image = ":image",
repository = "index.docker.io/org/image"
)
```

`repository` attribute can be overridden using the `--repository` flag.

```starlark
oci_image(
name = "image"
)

cosign_sign(
name = "sign",
image = ":image",
repository = "index.docker.io/org/image"
)
```

run `bazel run :sign -- --repository=index.docker.io/org/test`


**ATTRIBUTES**


| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="cosign_sign-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
| <a id="cosign_sign-image"></a>image | Label to an oci_image | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
| <a id="cosign_sign-repository"></a>repository | Repository URL where the image will be signed at. eg: index.docker.io/&lt;user&gt;/image. digests and tags are disallowed. | String | required | |


2 changes: 1 addition & 1 deletion example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ genrule(
}),
output_to_bindir = True,
toolchains = [
"@oci_crane_toolchains//:resolved_toolchain",
"@oci_crane_toolchains//:current_toolchain",
],
)
Loading

0 comments on commit a3bf6e7

Please sign in to comment.