From 7b1639b2194a8bfbb0daedf1cbdfc4ebef5f6b31 Mon Sep 17 00:00:00 2001 From: Sergio Correia Date: Mon, 18 May 2020 08:36:17 -0300 Subject: [PATCH] Introduce -y (assume yes) argument to clevis luks bind In order to simplify automated operations with e.g. ansible, it would be helpful to have a way to automate the creation of bindings with clevis. In simple scenarios, it's possible to download the advertisement from a tang server and pass it in the binding configuration, to do the binding offline, in the following way: curl -sfg http://tang.server/adv -o adv.jws clevis luks bind -d /dev/sda2 tang '{"url":"http://tang.server", "adv":"adv.jws}' However, for more complex scenarios using multiple servers with the sss pin, it becomes a lot more complicated to do the same thing and do the binding in an automated fashion. An alternative would be to use expect (tcl), but it can also be complicated. In this commit we introduce -y as a parameter to clevis luks bind, meanining _assume yes_. Essentially, this would make it so that the user would not have to manually trust tang key(s) by typing y/yes. Security-wise, it would be similar to downloading the advertisement manually and passing it to tang as the "adv" configuration option, something already supported. We already have a -f parameter, so we picked something different, not to change existing behavior and possibly break existing scripts. --- src/luks/clevis-luks-bind.1.adoc | 7 +- src/luks/clevis-luks-bind.in | 11 +++- src/luks/clevis-luks-regen | 4 +- src/luks/tests/assume-yes-luks1 | 81 ++++++++++++++++++++++++ src/luks/tests/assume-yes-luks2 | 81 ++++++++++++++++++++++++ src/luks/tests/meson.build | 2 + src/pins/sss/clevis-encrypt-sss.1.adoc | 14 +++- src/pins/sss/clevis-encrypt-sss.c | 30 ++++++--- src/pins/tang/clevis-encrypt-tang | 35 ++++++---- src/pins/tang/clevis-encrypt-tang.1.adoc | 11 +++- 10 files changed, 246 insertions(+), 30 deletions(-) create mode 100755 src/luks/tests/assume-yes-luks1 create mode 100755 src/luks/tests/assume-yes-luks2 diff --git a/src/luks/clevis-luks-bind.1.adoc b/src/luks/clevis-luks-bind.1.adoc index 336c0f4..438e517 100644 --- a/src/luks/clevis-luks-bind.1.adoc +++ b/src/luks/clevis-luks-bind.1.adoc @@ -9,7 +9,7 @@ clevis-luks-bind - Bind a LUKS device using the specified policy == SYNOPSIS -*clevis luks bind* [-f] -d DEV [-s SLT] [-k KEY] PIN CFG +*clevis luks bind* [-f] [-y] -d DEV [-s SLT] [-k KEY] PIN CFG == OVERVIEW @@ -34,6 +34,11 @@ Clevis LUKS unlockers. See link:clevis-luks-unlockers.7.adoc[*clevis-luks-unlock * *-f* : Do not prompt for LUKSMeta initialization +* *-y* : + Automatically answer yes for all questions. When using _tang_, it + causes the advertisement trust check to be skipped, which can be + useful in automated deployments + * *-d* _DEV_ : The LUKS device on which to perform binding diff --git a/src/luks/clevis-luks-bind.in b/src/luks/clevis-luks-bind.in index 89a5e22..8b8b5ee 100755 --- a/src/luks/clevis-luks-bind.in +++ b/src/luks/clevis-luks-bind.in @@ -33,12 +33,14 @@ function luks2_supported() { function usage() { exec >&2 echo - echo "Usage: clevis luks bind [-f] [-s SLT] [-k KEY] -d DEV PIN CFG" + echo "Usage: clevis luks bind [-f] [-y] [-s SLT] [-k KEY] -d DEV PIN CFG" echo echo "$SUMMARY": echo echo " -f Do not prompt for LUKSMeta initialization" echo + echo " -y Automatically answer yes for all questions" + echo echo " -d DEV The LUKS device on which to perform binding" echo echo " -s SLT The LUKS slot to use" @@ -55,12 +57,15 @@ if [ $# -eq 1 ] && [ "$1" == "--summary" ]; then fi FRC=() -while getopts ":hfd:s:k:" o; do +YES=() +while getopts ":fyd:s:k:" o; do case "$o" in f) FRC+=(-f);; d) DEV="$OPTARG";; s) SLT="$OPTARG";; k) KEY="$OPTARG";; + y) FRC+=(-f) + YES+=(-y);; *) usage;; esac done @@ -139,7 +144,7 @@ cryptsetup luksDump "$DEV" \ )")" # Encrypt the new key -jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG")" +jwe="$(echo -n "$key" | clevis encrypt "$PIN" "$CFG" "${YES}")" # If necessary, initialize the LUKS volume if [ "$luks_type" == "luks1" ] && ! luksmeta test -d "$DEV"; then diff --git a/src/luks/clevis-luks-regen b/src/luks/clevis-luks-regen index 44fd673..6071d85 100755 --- a/src/luks/clevis-luks-regen +++ b/src/luks/clevis-luks-regen @@ -110,7 +110,7 @@ if ! new_passphrase=$(generate_key "${DEV}"); then fi # Reencrypt the new password. -if ! jwe=$(clevis encrypt "${PIN}" "${CFG}" <<< "${new_passphrase}"); then +if ! jwe="$(clevis encrypt "${PIN}" "${CFG}" <<< "${new_passphrase}")"; then echo "Error using pin '${PIN}' with config '${CFG}'" >&2 exit 1 fi @@ -176,7 +176,7 @@ fi # Now make sure that we can unlock this device after the change. # If we can't, undo the changes. if ! cryptsetup open --test-passphrase --key-slot "${SLT}" "${DEV}" 2>/dev/null \ - <<< $(clevis luks pass -d "${DEV}" -s "${SLT}" 2>/dev/null); then + <<< "$(clevis luks pass -d "${DEV}" -s "${SLT}" 2>/dev/null)"; then echo "Invalid configuration detected after rebinding. Reverting changes." restore_device "${DEV}" "${TMP}" exit 1 diff --git a/src/luks/tests/assume-yes-luks1 b/src/luks/tests/assume-yes-luks1 new file mode 100755 index 0000000..ad9dea4 --- /dev/null +++ b/src/luks/tests/assume-yes-luks1 @@ -0,0 +1,81 @@ +#!/bin/bash -ex +# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2020 Red Hat, Inc. +# Author: Sergio Correia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +TEST=$(basename "${0}") +. tests-common-functions + +. clevis-luks-common-functions + +on_exit() { + local d + for d in "${TMP}" "${TMP2}"; do + [ ! -d "${d}" ] && continue + tang_stop "${d}" + rm -rf "${d}" + done +} + +trap 'on_exit' EXIT +trap 'on_exit' ERR + +TMP="$(mktemp -d)" + +port=$(get_random_port) +tang_run "${TMP}" "${port}" & +tang_wait_until_ready "${port}" + +url="http://${TANG_HOST}:${port}" + +cfg=$(printf '{"url":"%s"}' "$url") + +# LUKS1. +DEV="${TMP}/luks1-device" +new_device "luks1" "${DEV}" + +if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Bind should have succeeded." +fi + +if ! clevis_luks_unlock_device "${DEV}"; then + error "${TEST}: we were unable to unlock ${DEV}." +fi + +# Let's use a second tang server to test the sss pin. +TMP2="$(mktemp -d)" + +port2=$(get_random_port) +tang_run "${TMP2}" "${port2}" & +tang_wait_until_ready "${port2}" + +url2="http://${TANG_HOST}:${port2}" + +cfg2=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ + "${url1}" "${url2}") + +# LUKS1. +new_device "luks1" "${DEV}" +# Now let's test the sss pin with the two test tang servers we deployed. +if ! clevis luks bind -y -d "${DEV}" sss "${cfg2}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Bind should have succeeded." +fi + +# Unlock should still work now. +if ! clevis_luks_unlock_device "${DEV}"; then + error "${TEST}: we should still be able to unlock ${DEV}" +fi diff --git a/src/luks/tests/assume-yes-luks2 b/src/luks/tests/assume-yes-luks2 new file mode 100755 index 0000000..5c0edc3 --- /dev/null +++ b/src/luks/tests/assume-yes-luks2 @@ -0,0 +1,81 @@ +#!/bin/bash -ex +# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2020 Red Hat, Inc. +# Author: Sergio Correia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +TEST=$(basename "${0}") +. tests-common-functions + +. clevis-luks-common-functions + +on_exit() { + local d + for d in "${TMP}" "${TMP2}"; do + [ ! -d "${d}" ] && continue + tang_stop "${d}" + rm -rf "${d}" + done +} + +trap 'on_exit' EXIT +trap 'on_exit' ERR + +TMP="$(mktemp -d)" + +port=$(get_random_port) +tang_run "${TMP}" "${port}" & +tang_wait_until_ready "${port}" + +url="http://${TANG_HOST}:${port}" + +cfg=$(printf '{"url":"%s"}' "$url") + +# LUKS2. +DEV="${TMP}/luks2-device" +new_device "luks2" "${DEV}" + +if ! clevis luks bind -y -d "${DEV}" tang "${cfg}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Bind should have succeeded." +fi + +if ! clevis_luks_unlock_device "${DEV}"; then + error "${TEST}: we were unable to unlock ${DEV}." +fi + +# Let's use a second tang server to test the sss pin. +TMP2="$(mktemp -d)" + +port2=$(get_random_port) +tang_run "${TMP2}" "${port2}" & +tang_wait_until_ready "${port2}" + +url2="http://${TANG_HOST}:${port2}" + +cfg2=$(printf '{"t":1,"pins":{"tang":[{"url":"%s"},{"url":"%s"}]}}' \ + "${url1}" "${url2}") + +# LUKS2. +new_device "luks2" "${DEV}" +# Now let's test the sss pin with the two test tang servers we deployed. +if ! clevis luks bind -y -d "${DEV}" sss "${cfg2}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Bind should have succeeded." +fi + +# Unlock should still work now. +if ! clevis_luks_unlock_device "${DEV}"; then + error "${TEST}: we should still be able to unlock ${DEV}" +fi diff --git a/src/luks/tests/meson.build b/src/luks/tests/meson.build index dbef9bf..4795488 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -85,6 +85,7 @@ endif if has_tang test('unlock-tang-luks1', find_program('unlock-tang-luks1'), env: env, timeout: 90) + test('assume-yes-luks1', find_program('assume-yes-luks1'), env: env) endif test('pass-tang-luks1', find_program('pass-tang-luks1'), env: env) test('backup-restore-luks1', find_program('backup-restore-luks1'), env: env) @@ -108,6 +109,7 @@ if luksmeta_data.get('OLD_CRYPTSETUP') == '0' if has_tang test('unlock-tang-luks2', find_program('unlock-tang-luks2'), env: env, timeout: 120) + test('assume-yes-luks2', find_program('assume-yes-luks2'), env: env, timeout: 60) endif test('pass-tang-luks2', find_program('pass-tang-luks2'), env: env, timeout: 60) test('backup-restore-luks2', find_program('backup-restore-luks2'), env:env, timeout: 90) diff --git a/src/pins/sss/clevis-encrypt-sss.1.adoc b/src/pins/sss/clevis-encrypt-sss.1.adoc index 7144e7e..7152144 100644 --- a/src/pins/sss/clevis-encrypt-sss.1.adoc +++ b/src/pins/sss/clevis-encrypt-sss.1.adoc @@ -5,11 +5,11 @@ CLEVIS-ENCRYPT-SSS(1) == NAME -clevis-encrypt-sss - Encrypts using a Shamir's Secret Sharing policy +clevis-encrypt-sss - Encrypts using a Shamir's Secret Sharing policy == SYNOPSIS -*clevis encrypt sss* CONFIG < PT > JWE +*clevis encrypt sss* CONFIG [-y] < PT > JWE == OVERVIEW @@ -52,6 +52,16 @@ The format of the *pins* property is as follows: When the list version of the format is used, multiple pins of that type will receive key fragments. +== OPTIONS + +* *-y* : + Automatically answer yes for all questions. For the _tang_ pin, it will + skip the advertisement trust check, which can be useful in automated + deployments: + + $ cfg='{"t":1,"pins":{"tang":[{"url":...},{"url":...}]}}' + $ clevis encrypt sss "$cfg" -y < PT > JWE + == SEE ALSO link:clevis-encrypt-tang.1.adoc[*clevis-encrypt-tang*(1)], diff --git a/src/pins/sss/clevis-encrypt-sss.c b/src/pins/sss/clevis-encrypt-sss.c index d6f2c2c..531e918 100644 --- a/src/pins/sss/clevis-encrypt-sss.c +++ b/src/pins/sss/clevis-encrypt-sss.c @@ -86,9 +86,9 @@ npins(json_t *pins) } static json_t * -encrypt_frag(json_t *sss, const char *pin, const json_t *cfg) +encrypt_frag(json_t *sss, const char *pin, const json_t *cfg, int assume_yes) { - char *args[] = { "clevis", "encrypt", (char *) pin, NULL, NULL }; + char *args[] = { "clevis", "encrypt", (char *) pin, NULL, NULL, NULL }; json_auto_t *jwe = json_string(""); str_auto_t *str = NULL; uint8_t *pnt = NULL; @@ -100,6 +100,10 @@ encrypt_frag(json_t *sss, const char *pin, const json_t *cfg) if (!str) return NULL; + if (assume_yes) { + args[4] = "-y"; + } + pnt = sss_point(sss, &pntl); if (!pnt) return NULL; @@ -137,7 +141,7 @@ encrypt_frag(json_t *sss, const char *pin, const json_t *cfg) } static json_t * -encrypt_frags(json_int_t t, json_t *pins) +encrypt_frags(json_int_t t, json_t *pins, int assume_yes) { const char *pname = NULL; json_auto_t *sss = NULL; @@ -172,7 +176,7 @@ encrypt_frags(json_int_t t, json_t *pins) json_array_foreach(pcfgs, i, pcfg) { json_auto_t *jwe = NULL; - jwe = encrypt_frag(sss, pname, pcfg); + jwe = encrypt_frag(sss, pname, pcfg, assume_yes); if (!jwe) return NULL; @@ -201,14 +205,24 @@ main(int argc, char *argv[]) const char *iv = NULL; json_t *pins = NULL; json_int_t t = 1; + int assume_yes = 0; if (argc == 2 && strcmp(argv[1], "--summary") == 0) { fprintf(stdout, "%s\n", SUMMARY); return EXIT_SUCCESS; } - if (isatty(STDIN_FILENO) || argc != 2) - goto usage; + if (isatty(STDIN_FILENO) || argc != 2) { + if (argc != 3) { + goto usage; + } + + if (strcmp(argv[2], "-y") == 0) { + assume_yes = 1; + } else if (strlen(argv[2]) > 0) { + goto usage; + } + } /* Parse configuration. */ cfg = json_loads(argv[1], 0, NULL); @@ -228,7 +242,7 @@ main(int argc, char *argv[]) return EXIT_FAILURE; } - sss = encrypt_frags(t, pins); + sss = encrypt_frags(t, pins, assume_yes); if (!sss) return EXIT_FAILURE; @@ -287,7 +301,7 @@ main(int argc, char *argv[]) usage: fprintf(stderr, "\n"); - fprintf(stderr, "Usage: clevis encrypt sss CONFIG < PLAINTEXT > JWE\n"); + fprintf(stderr, "Usage: clevis encrypt sss CONFIG [-y] < PLAINTEXT > JWE\n"); fprintf(stderr, "\n"); fprintf(stderr, "%s\n", SUMMARY); fprintf(stderr, "\n"); diff --git a/src/pins/tang/clevis-encrypt-tang b/src/pins/tang/clevis-encrypt-tang index 378b25d..4a43f1f 100755 --- a/src/pins/tang/clevis-encrypt-tang +++ b/src/pins/tang/clevis-encrypt-tang @@ -28,10 +28,14 @@ fi if [ -t 0 ]; then exec >&2 echo - echo "Usage: clevis encrypt tang CONFIG < PLAINTEXT > JWE" + echo "Usage: clevis encrypt tang CONFIG [-y] < PLAINTEXT > JWE" echo echo "$SUMMARY" echo + echo " -y Use this option for skipping the advertisement" + echo " trust check. This can be useful in automated" + echo " deployments" + echo echo "This command uses the following configuration properties:" echo echo " url: The base URL of the Tang server (REQUIRED)" @@ -60,6 +64,9 @@ if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then exit 1 fi +trust= +[ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes + if ! url="$(jose fmt -j- -Og url -u- <<< "$cfg")"; then echo "Missing the required 'url' property!" >&2 exit 1 @@ -100,18 +107,20 @@ if ! jose jws ver -i "$jws" -k- -a <<< "$ver"; then fi ### Check advertisement trust -if [ -z "$thp" ]; then - echo "The advertisement contains the following signing keys:" >&2 - echo >&2 - jose jwk thp -i- <<< "$ver" >&2 - echo >&2 - read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty - [[ "$ans" =~ ^[yY]$ ]] || exit 1 - -elif [ "$thp" != "any" ] && \ - ! jose jwk thp -i- -f "$thp" -o /dev/null <<< "$ver"; then - echo "Trusted JWK '$thp' did not sign the advertisement!" >&2 - exit 1 +if [ -z "${trust}" ]; then + if [ -z "$thp" ]; then + echo "The advertisement contains the following signing keys:" >&2 + echo >&2 + jose jwk thp -i- <<< "$ver" >&2 + echo >&2 + read -r -p "Do you wish to trust these keys? [ynYN] " ans < /dev/tty + [[ "$ans" =~ ^[yY]$ ]] || exit 1 + + elif [ "$thp" != "any" ] && \ + ! jose jwk thp -i- -f "$thp" -o /dev/null <<< "$ver"; then + echo "Trusted JWK '$thp' did not sign the advertisement!" >&2 + exit 1 + fi fi ### Perform encryption diff --git a/src/pins/tang/clevis-encrypt-tang.1.adoc b/src/pins/tang/clevis-encrypt-tang.1.adoc index 276575f..c34d109 100644 --- a/src/pins/tang/clevis-encrypt-tang.1.adoc +++ b/src/pins/tang/clevis-encrypt-tang.1.adoc @@ -9,7 +9,7 @@ clevis-encrypt-tang - Encrypts using a Tang binding server policy == SYNOPSIS -*clevis encrypt tang* CONFIG < PT > JWE +*clevis encrypt tang* CONFIG [-y] < PT > JWE == OVERVIEW @@ -76,6 +76,15 @@ This command uses the following configuration properties: * *adv* (object) : A trusted advertisement (raw JSON) +== OPTIONS + +* *-y* : + Automatically answer yes for all questions. Use this option for skipping + the advertisement trust check. This can be useful in automated deployments: + + $ clevis encrypt tang '{"url":...}' -y < PT > JWE + + == SEE ALSO link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] -- 2.18.4