Skip to content

Commit c963cf6

Browse files
authored
Merge pull request #303 from trz42/sign_uploads
sign artefact and metadata file and upload signatures
2 parents 7319466 + 8a8963c commit c963cf6

File tree

9 files changed

+273
-24
lines changed

9 files changed

+273
-24
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,13 @@ variables) that are allowed to be specified in a PR command with the
477477
`exportvariable` filters must be used (one per variable). These variables will
478478
be exported into the build environment before running the bot/build.sh script.
479479

480+
The bot build script makes use of the variable `SKIP_TESTS` to determine if
481+
ReFrame tests shall be skipped or not. Default is not to skip them. To allow the
482+
use of the variable the setting could look like
483+
```
484+
allowed_exportvars = ["SKIP_TESTS=yes", "SKIP_TESTS=no"]
485+
```
486+
480487

481488
#### `[bot_control]` section
482489

@@ -508,6 +515,35 @@ artefact_upload_script = PATH_TO_EESSI_BOT/scripts/eessi-upload-to-staging
508515
```
509516
`artefact_upload_script` provides the location for the script used for uploading built software packages to an S3 bucket.
510517

518+
```
519+
signing =
520+
{
521+
REPO_ID: {
522+
"script": PATH_TO_SIGN_SCRIPT,
523+
"key": PATH_TO_KEY_FILE,
524+
"container_runtime": PATH_TO_CONTAINER_RUNTIME
525+
}, ...
526+
}
527+
```
528+
`signing` provides a setting for signing artefacts. The value uses a JSON-like format
529+
with `REPO_ID` being the repository ID. Repository IDs are defined in a file
530+
`repos.cfg` (see setting `repos_cfg_dir`), `script` provides the location of the
531+
script that is used to sign a file. If the location is a relative path, the script
532+
must reside in the checked out pull request of the target repository (e.g.,
533+
EESSI/software-layer). `key` points to the file of the key being used
534+
for signing. The bot calls the script with the two arguments:
535+
1. private key (as provided by the attribute 'key')
536+
2. path to the file to be signed (the upload script will determine that)
537+
NOTE (on `container_runtime`), signing requires a recent installation of OpenSSH
538+
(8.2 or newer). If the frontend where the event handler runs does not have that
539+
version installed, you can specify a container runtime via the `container_runtime`
540+
attribute below. Currently, only Singularity or Apptainer are supported.
541+
Note (on the key), make sure the file permissions are restricted to `0600` (only
542+
readable+writable by the file owner, or the signing will likely fail.
543+
Note (on json format), make sure no trailing commas are used after any elements
544+
or parsing/loading the json will likely fail. Also, the whole value should start
545+
at a new line and be indented as shown above.
546+
511547
```
512548
endpoint_url = URL_TO_S3_SERVER
513549
```

app.cfg.example

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,17 @@ no_build_permission_comment = Label `bot:build` has been set by user `{build_lab
161161
allow_update_submit_opts = false
162162

163163
# defines which name-value pairs (environment variables) are allowed to be
164-
# exported into the build environment via `exportvariable` filters
165-
allowed_exportvars = ["NAME1=value_1a", "NAME1=value_1b", "NAME2=value_2"]
164+
# exported into the build environment via 'exportvariable' filters
165+
# The bot build script makes use of the variable 'SKIP_TESTS' to determine if
166+
# ReFrame tests shall be skipped or not. Default value is 'no'. If the value is
167+
# 'yes' and the exportvariable filter is added to a bot build command
168+
# ('export:SKIP_TESTS=yes'), ReFrame tests are skipped.
169+
# NOTE, the setting is optional and commented by default. If you want to enable
170+
# this feature ('exportvariable' filters), uncomment the line below and define
171+
# meaningful key-value pair(s). For example, to enable the use of
172+
# 'exportvariable:SKIP_TESTS=yes' as a filter, the key-value pair would be
173+
# "SKIP_TESTS=yes".
174+
# allowed_exportvars = ["NAME1=value_1a", "NAME1=value_1b", "NAME2=value_2"]
166175

167176

168177
[deploycfg]
@@ -185,6 +194,31 @@ endpoint_url = URL_TO_S3_SERVER
185194
# like: bucket_name = {"eessi-pilot-2023.06": "eessi-staging-pilot-2023.06", "eessi.io-2023.06": "software.eessi.io-2023.06"}
186195
bucket_name = eessi-staging
187196

197+
# settings for signing artefacts with JSON-like format
198+
# REPO_ID: { "script": PATH_TO_SIGN_SCRIPT, "key": PATH_TO_KEY_FILE, "container_runtime": PATH_TO_CONTAINER_RUNTIME }
199+
# If PATH_TO_SIGN_SCRIPT is a relative path, the script must reside in the
200+
# checked out pull request of the target repository (e.g.,
201+
# EESSI/software-layer).
202+
# The bot calls the script with the two arguments:
203+
# 1. private key (as provided by the attribute 'key')
204+
# 2. path to the file to be signed (the upload script will determine that)
205+
# NOTE (on "container_runtime"), signing requires a recent installation of OpenSSH
206+
# (8.2 or newer). If the frontend where the event handler runs does not have that
207+
# version installed, you can specify a container runtime via the 'container_runtime'
208+
# attribute below. Currently, only Singularity or Apptainer are supported.
209+
# NOTE (on the key), make sure the file permissions are restricted to `0600` (only
210+
# readable+writable by the file owner, or the signing will likely fail.
211+
# Note (on json format), make sure no trailing commas are used after any elements
212+
# or parsing/loading the json will likely fail. Also, the whole value should start
213+
# at a new line and be indented as shown below.
214+
signing =
215+
{
216+
"eessi.io-2023.06-software: {
217+
"script": PATH_TO_SIGN_SCRIPT,
218+
"key": PATH_TO_EESSI_BOT/config/user-site-system.key,
219+
"container_runtime": PATH_TO_CONTAINER_RUNTIME
220+
}
221+
}
188222
# upload policy: defines what policy is used for uploading built artefacts
189223
# to an S3 bucket
190224
# 'all' ..: upload all artefacts (mulitple uploads of the same artefact possible)

eessi_bot_event_handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
# config.DEPLOYCFG_SETTING_ENDPOINT_URL, # optional
7878
config.DEPLOYCFG_SETTING_METADATA_PREFIX, # (required)
7979
config.DEPLOYCFG_SETTING_NO_DEPLOY_PERMISSION_COMMENT, # required
80+
# config.DEPLOYCFG_SETTING_SIGNING, # optional
8081
config.DEPLOYCFG_SETTING_UPLOAD_POLICY], # required
8182
config.SECTION_DOWNLOAD_PR_COMMENTS: [
8283
config.DOWNLOAD_PR_COMMENTS_SETTING_CURL_FAILURE, # required

scripts/bot-build.slurm

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
# - the directory may contain any additional files references in job.cfg,
2424
# for example, repos.cfg and configuration file bundles for repositories
2525

26+
# set default for SKIP_TESTS (don't skip ReFrame tests)
27+
SKIP_TESTS=no
28+
2629
echo "Starting bot-build.slurm"
2730
EXPORT_VARS_SCRIPT=cfg/export_vars.sh
2831
if [ -f ${EXPORT_VARS_SCRIPT} ]; then
@@ -108,14 +111,19 @@ artefacts =
108111
EOF
109112
fi
110113
echo "check build step finished"
111-
TEST_SCRIPT=bot/test.sh
112-
if [ -f ${TEST_SCRIPT} ]; then
113-
echo "${TEST_SCRIPT} script found in '${PWD}', so running it!"
114-
${TEST_SCRIPT}
115-
echo "${TEST_SCRIPT} finished"
116-
else
117-
echo "could not find ${TEST_SCRIPT} script in '${PWD}'" >&2
114+
115+
# SKIP_TESTS can be defined as export variable in the bot's config and then added to bot commands (export:SKIP_TESTS=yes)
116+
if [[ "${SKIP_TESTS}" != "yes" ]]; then
117+
TEST_SCRIPT=bot/test.sh
118+
if [ -f ${TEST_SCRIPT} ]; then
119+
echo "${TEST_SCRIPT} script found in '${PWD}', so running it!"
120+
${TEST_SCRIPT}
121+
echo "${TEST_SCRIPT} finished"
122+
else
123+
echo "could not find ${TEST_SCRIPT} script in '${PWD}'" >&2
124+
fi
118125
fi
126+
119127
CHECK_TEST_SCRIPT=bot/check-test.sh
120128
if [ -f ${CHECK_TEST_SCRIPT} ]; then
121129
echo "${CHECK_TEST_SCRIPT} script found in '${PWD}', so running it!"

scripts/eessi-upload-to-staging

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ function display_help
8383
echo " ingestion procedure" >&2
8484
echo " -l | --list-variables - list variables that are available" >&2
8585
echo " for expansion" >&2
86+
echo " -k | --sign-key SCRIPT_KEY - specify location of the key to be" >&2
87+
echo " used to sign artefacts and metadata" >&2
88+
echo " files [optional; default: don't sign]" >&2
8689
echo " -m | --metadata-prefix PREFIX - a directory to which the metadata" >&2
8790
echo " file shall be uploaded; BASH variable" >&2
8891
echo " expansion will be applied; arg '-l'" >&2
@@ -93,6 +96,13 @@ function display_help
9396
echo " link the upload to a PR" >&2
9497
echo " -r | --repository FULL_NAME - a repository name ACCOUNT/REPONAME;" >&2
9598
echo " used to link the upload to a PR" >&2
99+
echo " -s | --sign-script SCRIPT_PATH - path to script that is used to sign" >&2
100+
echo " artefacts and metadata files. The" >&2
101+
echo " script is called with two arguments:" >&2
102+
echo " KEY file_to_sign. The KEY is the one" >&2
103+
echo " provided via option --sign-key. The" >&2
104+
echo " latter is determined by this script." >&2
105+
echo " [optional; default: don't sign]" >&2
96106
}
97107

98108
if [[ $# -lt 1 ]]; then
@@ -120,6 +130,8 @@ endpoint_url=
120130
pr_comment_id="none"
121131
pull_request_number="none"
122132
github_repository="EESSI/software-layer"
133+
sign_key=
134+
sign_script=
123135

124136
# provided via options in the bot's config file app.cfg and/or command line argument
125137
metadata_prefix=
@@ -155,6 +167,14 @@ while [[ $# -gt 0 ]]; do
155167
pr_comment_id="$2"
156168
shift 2
157169
;;
170+
-k|--sign-key)
171+
sign_key=$2
172+
if [[ ! -r "${sign_key}" ]]; then
173+
echo "Error: SSH key '${sign_key}' to be used for signing doesn't exist or cannot be read" >&2
174+
exit 1
175+
fi
176+
shift 2
177+
;;
158178
-m|--metadata-prefix)
159179
metadata_prefix="$2"
160180
shift 2
@@ -171,6 +191,14 @@ while [[ $# -gt 0 ]]; do
171191
github_repository="$2"
172192
shift 2
173193
;;
194+
-s|--sign-script)
195+
sign_script=$2
196+
if [[ ! -x "${sign_script}" ]]; then
197+
echo "Error: Script '${sign_script}' to be used for signing doesn't exist or is not executable" >&2
198+
exit 1
199+
fi
200+
shift 2
201+
;;
174202
-*|--*)
175203
echo "Error: Unknown option: $1" >&2
176204
exit 1
@@ -185,6 +213,21 @@ done
185213
# restore potentially parsed filename(s) into $*
186214
set -- "${POSITIONAL_ARGS[@]}"
187215

216+
# ensure that either none or both of $sign_key and $sign_script are defined
217+
if [[ -n "${sign_key}" ]] && [[ -n "${sign_script}" ]]; then
218+
sign=1
219+
elif [[ -n "${sign_key}" ]]; then
220+
sign=0
221+
echo "Error: Signing requires a key (${sign_key}) AND a script (${sign_script}); likely the bot config is incomplete" >&2
222+
exit 1
223+
elif [[ -n "${sign_script}" ]]; then
224+
sign=0
225+
echo "Error: Signing requires a key (${sign_key}) AND a script (${sign_script}); likely the bot config is incomplete" >&2
226+
exit 1
227+
else
228+
sign=0
229+
fi
230+
188231
# infer bucket_base:
189232
# if endpoint_url is not set (assume AWS S3 is used),
190233
# bucket_base=https://${bucket_name}.s3.amazonaws.com/
@@ -217,6 +260,33 @@ for file in "$*"; do
217260
aws_path=$(envsubst <<< "${artefact_prefix}")
218261
fi
219262
aws_file=$(basename ${file})
263+
# 1st sign artefact, and upload signature
264+
if [[ "${sign}" = "1" ]]; then
265+
# sign artefact
266+
${sign_script} sign ${sign_key} ${file}
267+
# TODO check if signing worked (just check exit code == 0)
268+
sig_file=${file}.sig
269+
aws_sig_file=${aws_file}.sig
270+
271+
# uploading signature
272+
echo " store artefact signature at ${aws_path}/${aws_sig_file}"
273+
upload_to_staging_bucket \
274+
"${sig_file}" \
275+
"${bucket_name}" \
276+
"${aws_path}/${aws_sig_file}" \
277+
"${endpoint_url}"
278+
else
279+
echo "no signing method defined; not signing artefact"
280+
fi
281+
282+
echo Uploading to "${url}"
283+
echo " store artefact at ${aws_path}/${aws_file}"
284+
upload_to_staging_bucket \
285+
"${file}" \
286+
"${bucket_name}" \
287+
"${aws_path}/${aws_file}" \
288+
"${endpoint_url}"
289+
220290
echo "Creating metadata file"
221291
url="${bucket_base}/${aws_path}/${aws_file}"
222292
echo "create_metadata_file file=${file} \
@@ -229,24 +299,35 @@ for file in "$*"; do
229299
"${github_repository}" \
230300
"${pull_request_number}" \
231301
"${pr_comment_id}")
302+
aws_metadata_file=${aws_file}.meta.txt
303+
# TODO check that creating the metadata file succeeded
232304
echo "metadata:"
233305
cat ${metadata_file}
234306

235-
echo Uploading to "${url}"
236-
echo " store artefact at ${aws_path}/${aws_file}"
237-
upload_to_staging_bucket \
238-
"${file}" \
239-
"${bucket_name}" \
240-
"${aws_path}/${aws_file}" \
241-
"${endpoint_url}"
242-
243307
if [ -z ${metadata_prefix} ]; then
244308
aws_path=${legacy_aws_path}
245309
else
246310
export pull_request_number
247311
export github_repository
248312
aws_path=$(envsubst <<< "${metadata_prefix}")
249313
fi
314+
# 2nd sign metadata file, and upload signature
315+
if [[ "${sign}" = "1" ]]; then
316+
# sign metadata file
317+
${sign_script} sign ${sign_key} ${metadata_file}
318+
# TODO check if signing worked (just check exit code == 0)
319+
sig_metadata_file=${metadata_file}.sig
320+
aws_sig_metadata_file=${aws_metadata_file}.sig
321+
322+
echo " store metadata signature at ${aws_path}/${aws_sig_metadata_file}"
323+
upload_to_staging_bucket \
324+
"${sig_metadata_file}" \
325+
"${bucket_name}" \
326+
"${aws_path}/${aws_sig_metadata_file}" \
327+
"${endpoint_url}"
328+
else
329+
echo "no signing method defined; not signing metadata file"
330+
fi
250331
echo " store metadata file at ${aws_path}/${aws_file}.meta.txt"
251332
upload_to_staging_bucket \
252333
"${metadata_file}" \

0 commit comments

Comments
 (0)