Skip to content

Commit

Permalink
libzfs: add keylocation=https://, backed by fetch(3) or libcurl
Browse files Browse the repository at this point in the history
Signed-off-by: Ahelenia Ziemiańska <[email protected]>
Ref: openzfs#9543
Closes openzfs#9947
  • Loading branch information
nabijaczleweli committed Apr 28, 2021
1 parent b0269cd commit 774436d
Show file tree
Hide file tree
Showing 20 changed files with 7,150 additions and 4,258 deletions.
69 changes: 69 additions & 0 deletions config/user-libfetch.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
dnl #
dnl # Check for a libfetch - either fetch(3) or libcurl.
dnl #
dnl # There are two configuration dimensions:
dnl # * fetch(3) vs libcurl
dnl # * static vs dynamic
dnl #
dnl # fetch(3) is only dynamic, we use sover 6,
dnl # which first appeared in 8.0-RELEASE.
dnl #
dnl # libcurl development packages include curl-config(1) – we want:
dnl # * HTTPS support
dnl # * version at least 7.10 ("October 2006"), for sover 4
dnl # * to decide if it's static or not
dnl #
AC_DEFUN([ZFS_AC_CONFIG_USER_LIBFETCH], [
AC_MSG_CHECKING([for libfetch])
LIBFETCH_LIBS=
LIBFETCH_IS_FETCH=0
LIBFETCH_IS_LIBCURL=0
LIBFETCH_DYNAMIC=0
LIBFETCH_SONAME=
have_libfetch=
saved_libs="$LIBS"
LIBS="$LIBS -lfetch"
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#include <sys/param.h>
#include <stdio.h>
#include <fetch.h>
]], [fetchGetURL("", "");])], [
have_libfetch=1
LIBFETCH_IS_FETCH=1
LIBFETCH_DYNAMIC=1
LIBFETCH_SONAME='"libfetch.so.6"'
LIBFETCH_LIBS="-ldl"
AC_MSG_RESULT([fetch(3)])
], [])
LIBS="$saved_libs"
if test -z "$have_libfetch"; then
if curl-config --protocols 2>/dev/null | grep -q HTTPS &&
test "$(("0x$(curl-config --vernum)"))" -ge "$((0x071000))"; then
have_libfetch=1
LIBFETCH_IS_LIBCURL=1
if test "$(curl-config --built-shared)" = "yes"; then
LIBFETCH_DYNAMIC=1
LIBFETCH_SONAME='"libcurl.so.4"'
LIBFETCH_LIBS="-ldl"
AC_MSG_RESULT([libcurl])
else
LIBFETCH_LIBS="$(curl-config --libs)"
AC_MSG_RESULT([libcurl (static)])
fi
CCFLAGS="$CCFLAGS $(curl-config --cflags)"
fi
fi
if test -z "$have_libfetch"; then
AC_MSG_RESULT([none])
fi
AC_SUBST([LIBFETCH_LIBS])
AC_DEFINE_UNQUOTED([LIBFETCH_IS_FETCH], [$LIBFETCH_IS_FETCH], [libfetch is fetch(3)])
AC_DEFINE_UNQUOTED([LIBFETCH_IS_LIBCURL], [$LIBFETCH_IS_LIBCURL], [libfetch is libcurl])
AC_DEFINE_UNQUOTED([LIBFETCH_DYNAMIC], [$LIBFETCH_DYNAMIC], [whether the chosen libfetch is to be loaded at run-time])
AC_DEFINE_UNQUOTED([LIBFETCH_SONAME], [$LIBFETCH_SONAME], [soname of chosen libfetch])
])
1 change: 1 addition & 0 deletions config/user.m4
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [
ZFS_AC_CONFIG_USER_LIBCRYPTO
ZFS_AC_CONFIG_USER_LIBAIO
ZFS_AC_CONFIG_USER_LIBATOMIC
ZFS_AC_CONFIG_USER_LIBFETCH
ZFS_AC_CONFIG_USER_CLOCK_GETTIME
ZFS_AC_CONFIG_USER_PAM
ZFS_AC_CONFIG_USER_RUNSTATEDIR
Expand Down
2 changes: 2 additions & 0 deletions include/libzfs_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ struct libzfs_handle {
boolean_t libzfs_prop_debug;
regex_t libzfs_urire;
uint64_t libzfs_max_nvlist;
void *libfetch;
char *libfetch_load_error;
};

struct zfs_handle {
Expand Down
2 changes: 1 addition & 1 deletion lib/libzfs/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ libzfs_la_LIBADD = \
$(abs_top_builddir)/lib/libnvpair/libnvpair.la \
$(abs_top_builddir)/lib/libuutil/libuutil.la

libzfs_la_LIBADD += -lm $(LIBCRYPTO_LIBS) $(ZLIB_LIBS) $(LTLIBINTL)
libzfs_la_LIBADD += -lm $(LIBCRYPTO_LIBS) $(ZLIB_LIBS) $(LIBFETCH_LIBS) $(LTLIBINTL)

libzfs_la_LDFLAGS = -pthread

Expand Down
11,042 changes: 6,800 additions & 4,242 deletions lib/libzfs/libzfs.abi

Large diffs are not rendered by default.

160 changes: 160 additions & 0 deletions lib/libzfs/libzfs_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
#include <signal.h>
#include <errno.h>
#include <openssl/evp.h>
#if LIBFETCH_DYNAMIC
#include <dlfcn.h>
#endif
#if LIBFETCH_IS_FETCH
#include <sys/param.h>
#include <stdio.h>
#include <fetch.h>
#elif LIBFETCH_IS_LIBCURL
#include <curl/curl.h>
#endif
#include <libzfs.h>
#include "libzfs_impl.h"
#include "zfeature_common.h"
Expand Down Expand Up @@ -59,9 +69,12 @@ static int caught_interrupt;

static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
static int get_key_material_https(libzfs_handle_t *, const char *, const char *,
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);

static zfs_uri_handler_t uri_handlers[] = {
{ "file", get_key_material_file },
{ "https", get_key_material_https },
{ NULL, NULL }
};

Expand Down Expand Up @@ -483,6 +496,153 @@ get_key_material_file(libzfs_handle_t *hdl, const char *uri,
return (ret);
}

static int
get_key_material_https(libzfs_handle_t *hdl, const char *uri,
const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
uint8_t **restrict buf, size_t *restrict len_out)
{
int ret = 0;
FILE *key = NULL;

if (strlen(uri) < 8) {
ret = EINVAL;
goto end;
}

#if LIBFETCH_DYNAMIC
#define LOAD_FUNCTION(func) \
__typeof__(func) *func = dlsym(hdl->libfetch, #func);

if (hdl->libfetch == NULL)
hdl->libfetch = dlopen(LIBFETCH_SONAME, RTLD_LAZY);

if (hdl->libfetch == NULL) {
hdl->libfetch = (void *)-1;
char *err = dlerror();
if (err)
hdl->libfetch_load_error = strdup(err);
}

if (hdl->libfetch == (void *)-1) {
ret = ENOSYS;
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Couldn't load %s: %s"),
LIBFETCH_SONAME, hdl->libfetch_load_error ?: "(?)");
goto end;
}

boolean_t ok;
#if LIBFETCH_IS_FETCH
LOAD_FUNCTION(fetchGetURL);
char *fetchLastErrString = dlsym(hdl->libfetch, "fetchLastErrString");

ok = fetchGetURL && fetchLastErrString;
#elif LIBFETCH_IS_LIBCURL
LOAD_FUNCTION(curl_easy_init);
LOAD_FUNCTION(curl_easy_setopt);
LOAD_FUNCTION(curl_easy_perform);
LOAD_FUNCTION(curl_easy_cleanup);
LOAD_FUNCTION(curl_easy_strerror);
LOAD_FUNCTION(curl_easy_getinfo);

ok = curl_easy_init && curl_easy_setopt && curl_easy_perform &&
curl_easy_cleanup && curl_easy_strerror && curl_easy_getinfo;
#endif
if (!ok) {
ret = ENOSYS;
goto end;
}
#endif

#if LIBFETCH_IS_FETCH
key = fetchGetURL(uri, "");
if (key == NULL) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Couldn't GET %s: %s"),
uri, fetchLastErrString);
ret = ENETDOWN;
}
#elif LIBFETCH_IS_LIBCURL
CURL *curl = curl_easy_init();
if (curl == NULL) {
ret = ENOTSUP;
goto end;
}

char *path;
if (asprintf(&path,
"%s/libzfs-XXXXXXXX.https", getenv("TMPDIR") ?: "/tmp") == -1) {
ret = ENOMEM;
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s"),
strerror(ret));
goto end;
}

int kfd = mkostemps(path, strlen(".https"), O_CLOEXEC);
if (kfd == -1) {
ret = errno;
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Couldn't create temporary file %s: %s"),
path, strerror(ret));
free(path);
goto end;
}

if ((key = fdopen(kfd, "r+")) == NULL) {
ret = errno;
free(path);
(void) close(kfd);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Couldn't reopen temporary file: %s"), strerror(ret));
goto end;
}
(void) unlink(path);
free(path);

char errbuf[CURL_ERROR_SIZE] = "";
(void) curl_easy_setopt(curl, CURLOPT_URL, uri);
(void) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
(void) curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 30000L);
(void) curl_easy_setopt(curl, CURLOPT_WRITEDATA, key);
(void) curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);

CURLcode res = curl_easy_perform(curl);

if (res != CURLE_OK) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Failed to connect to %s: %s"),
uri, strlen(errbuf) ? errbuf : curl_easy_strerror(res));
ret = ENETDOWN;
} else {
long resp = 200;
(void) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp);

if (resp < 200 || resp >= 300) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Couldn't GET %s: %ld"),
uri, resp);
ret = ENOENT;
} else
rewind(key);
}

curl_easy_cleanup(curl);
#else
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"No heylocation=https:// back-end."));
ret = ENOSYS;
#endif

end:
if (ret == 0)
ret = get_key_material_raw(key, keyformat, buf, len_out);

if (key != NULL)
fclose(key);

return (ret);
}

/*
* Attempts to fetch key material, no matter where it might live. The key
* material is allocated and returned in km_out. *can_retry_out will be set
Expand Down
8 changes: 8 additions & 0 deletions lib/libzfs/libzfs_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
#include <strings.h>
#include <unistd.h>
#include <math.h>
#if LIBFETCH_DYNAMIC
#include <dlfcn.h>
#endif
#include <sys/stat.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
Expand Down Expand Up @@ -1081,6 +1084,11 @@ libzfs_fini(libzfs_handle_t *hdl)
libzfs_core_fini();
regfree(&hdl->libzfs_urire);
fletcher_4_fini();
#if LIBFETCH_DYNAMIC
if (hdl->libfetch != (void *)-1 && hdl->libfetch != NULL)
(void) dlclose(hdl->libfetch);
free(hdl->libfetch_load_error);
#endif
free(hdl);
}

Expand Down
8 changes: 6 additions & 2 deletions man/man8/zfsprops.8
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ encryption suite cannot be changed after dataset creation, the keyformat can be
with
.Nm zfs Cm change-key .
.It Xo
.Sy keylocation Ns = Ns Sy prompt Ns | Ns Sy file:// Ns Em </absolute/file/path>
.Sy keylocation Ns = Ns Sy prompt Ns | Ns Sy file:// Ns Em </absolute/file/path> Ns | Ns Sy https:// Ns Em <address>
.Xc
Controls where the user's encryption key will be loaded from by default for
commands such as
Expand All @@ -1109,7 +1109,11 @@ to access the encrypted data (see
for details). This setting will also allow the key to be passed in via STDIN,
but users should be careful not to place keys which should be kept secret on
the command line. If a file URI is selected, the key will be loaded from the
specified absolute file path.
specified absolute file path. If an HTTPS URI is selected, it will be GETted
using
.Xr fetch 3 ,
libcurl, or nothing, depending on compile-time configuration and run-time
availability. All back-ends support HTTP Basic auth encoded in the URL.
.It Sy pbkdf2iters Ns = Ns Ar iterations
Controls the number of PBKDF2 iterations that a
.Sy passphrase
Expand Down
4 changes: 3 additions & 1 deletion module/zcommon/zfs_prop.c
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ zfs_prop_init(void)
"ENCROOT");
zprop_register_string(ZFS_PROP_KEYLOCATION, "keylocation",
"none", PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
"prompt | <file URI>", "KEYLOCATION");
"prompt | <file URI> | <https URL>", "KEYLOCATION");
zprop_register_string(ZFS_PROP_REDACT_SNAPS,
"redact_snaps", NULL, PROP_READONLY,
ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "<snapshot>[,...]",
Expand Down Expand Up @@ -936,6 +936,8 @@ zfs_prop_valid_keylocation(const char *str, boolean_t encrypted)
return (B_TRUE);
else if (strlen(str) > 8 && strncmp("file:///", str, 8) == 0)
return (B_TRUE);
else if (strlen(str) > 8 && strncmp("https://", str, 8) == 0)
return (B_TRUE);

return (B_FALSE);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The pre-requisites for running the ZFS Test Suite are:
* The ZFS Test Suite will add users and groups to test machine to
verify functionality. Therefore it is strongly advised that a
dedicated test machine, which can be a VM, be used for testing.
* ZFS configured with a libfetch back-end and an internet connection.

Once the pre-requisites are satisfied simply run the zfs-tests.sh script:

Expand Down Expand Up @@ -146,7 +147,7 @@ with the `zfs-tests.sh` wrapper script will look something like this:
Results Summary
SKIP 52
PASS 1129

Running Time: 02:35:33
Percent passed: 95.6%
Log directory: /var/tmp/test_results/20180515T054509
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dist_pkgdata_SCRIPTS = \
zfs_load-key.ksh \
zfs_load-key_all.ksh \
zfs_load-key_file.ksh \
zfs_load-key_https.ksh \
zfs_load-key_location.ksh \
zfs_load-key_noop.ksh \
zfs_load-key_recursive.ksh
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
# Copyright (c) 2017 Datto, Inc. All rights reserved.
#

# $PASSPHRASE, $HEXKEY, and $RAWKEY must be kept in sync
# with the corresponding files in this directory

export PASSPHRASE="password"
export PASSPHRASE1="password1"
export PASSPHRASE2="password2"
Expand Down
Loading

0 comments on commit 774436d

Please sign in to comment.