Blob Blame Raw
From 7b1639b2194a8bfbb0daedf1cbdfc4ebef5f6b31 Mon Sep 17 00:00:00 2001
From: Sergio Correia <scorreia@redhat.com>
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 <scorreia@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <scorreia@redhat.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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: <string>   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