Add support for signing with Sigstore

Fixes: https://github.com/redhat-actions/push-to-registry/issues/89
This commit is contained in:
Timothée Ravier 2023-11-24 15:53:18 +01:00
parent 1d13b5ac9b
commit 23b2687cd6
4 changed files with 72 additions and 0 deletions

View file

@ -32,6 +32,8 @@ Refer to the [`podman push`](http://docs.podman.io/en/latest/markdown/podman-man
| password | Password, encrypted password, or access token to use to log in to the registry. Required unless already logged in to the registry. | None | password | Password, encrypted password, or access token to use to log in to the registry. Required unless already logged in to the registry. | None
| tls-verify | Verify TLS certificates when contacting the registry. Set to `false` to skip certificate verification. | `true` | tls-verify | Verify TLS certificates when contacting the registry. Set to `false` to skip certificate verification. | `true`
| digestfile | After copying the image, write the digest of the resulting image to the file. The contents of this file are the digest output. | Auto-generated from image and tag | digestfile | After copying the image, write the digest of the resulting image to the file. The contents of this file are the digest output. | Auto-generated from image and tag
| sigstore-private-key | Sigstore private key to use to sign container images | None
| sign-passphrase | Passphrase to unlock the Sigstore private key | None
| extra-args | Extra args to be passed to podman push. Separate arguments by newline. Do not use quotes. | None | extra-args | Extra args to be passed to podman push. Separate arguments by newline. Do not use quotes. | None
<a id="image-tag-inputs"></a> <a id="image-tag-inputs"></a>

View file

@ -33,6 +33,12 @@ inputs:
By default, the filename will be determined from the image and tag. By default, the filename will be determined from the image and tag.
The contents of this file are the digest output. The contents of this file are the digest output.
required: false required: false
sigstore-private-key:
description: 'Sigstore private key to use to sign container images'
required: false
sign-passphrase:
description: 'Passphrase to unlock the Sigstore private key'
required: false
extra-args: extra-args:
description: | description: |
Extra args to be passed to podman push. Extra args to be passed to podman push.

View file

@ -52,6 +52,18 @@ export enum Inputs {
* Default: None. * Default: None.
*/ */
USERNAME = "username", USERNAME = "username",
/**
* Sigstore private key to use to sign container images
* Required: false
* Default: None.
*/
SIGSTORE_PRIVATE_KEY = "sigstore-private-key",
/**
* Passphrase to unlock the Sigstore private key
* Required: false
* Default: None.
*/
SIGN_PASSPHRASE = "sign-passphrase",
} }
export enum Outputs { export enum Outputs {

View file

@ -209,6 +209,33 @@ async function run(): Promise<void> {
} }
} }
const sigstorePrivateKey = core.getInput(Inputs.SIGSTORE_PRIVATE_KEY);
const sigstorePrivateKeyFile = path.join(process.env.RUNNER_TEMP || "", "sigstore_private_key");
if (sigstorePrivateKey) {
// Write sigstore private key to a temporary file in $RUNNER_TEMP that
// will be removed after the image is pushed.
try {
await fs.promises.writeFile(sigstorePrivateKeyFile, sigstorePrivateKey);
}
catch (err) {
throw new Error(`Could not write sigstore private key to temporary file `
+ `"${sigstorePrivateKeyFile}": ${err}`);
}
}
const signPassphrase = core.getInput(Inputs.SIGN_PASSPHRASE);
const signPassphraseFile = path.join(process.env.RUNNER_TEMP || "", "sign_passphrase");
if (signPassphrase || sigstorePrivateKey) {
// Write passphrase (empty string if not provided) to a temporary file
// in $RUNNER_TEMP that will be removed after the image is pushed.
try {
await fs.promises.writeFile(signPassphraseFile, signPassphrase || "");
}
catch (err) {
throw new Error(`Could not write sign passphrase to temporary file `
+ `"${signPassphraseFile}": ${err}`);
}
}
let pushMsg = `⏳ Pushing "${sourceImages.join(", ")}" to "${destinationImages.join(", ")}" respectively`; let pushMsg = `⏳ Pushing "${sourceImages.join(", ")}" to "${destinationImages.join(", ")}" respectively`;
if (username) { if (username) {
pushMsg += ` as "${username}"`; pushMsg += ` as "${username}"`;
@ -269,11 +296,36 @@ async function run(): Promise<void> {
args.push(`--creds=${creds}`); args.push(`--creds=${creds}`);
} }
if (sigstorePrivateKey) {
args.push("--sign-by-sigstore-private-key");
args.push(sigstorePrivateKeyFile);
}
if (signPassphrase || sigstorePrivateKey) {
args.push("--sign-passphrase-file");
args.push(signPassphraseFile);
}
await execute(await getPodmanPath(), args); await execute(await getPodmanPath(), args);
core.info(`✅ Successfully pushed "${sourceImages[i]}" to "${destinationImages[i]}"`); core.info(`✅ Successfully pushed "${sourceImages[i]}" to "${destinationImages[i]}"`);
registryPathList.push(destinationImages[i]); registryPathList.push(destinationImages[i]);
try {
await fs.promises.unlink(sigstorePrivateKeyFile);
}
catch (err) {
core.warning(`Failed to remove temporary file used to store sigstore private key `
+ `"${sigstorePrivateKeyFile}": ${err}`);
}
try {
await fs.promises.unlink(signPassphraseFile);
}
catch (err) {
core.warning(`Failed to remove temporary file used to store sign passphrase `
+ `"${signPassphraseFile}": ${err}`);
}
try { try {
const digest = (await fs.promises.readFile(digestFile)).toString(); const digest = (await fs.promises.readFile(digestFile)).toString();
core.info(digest); core.info(digest);