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 <nabijaczleweli@nabijaczleweli.xyz>
Ref: openzfs#9543
Closes openzfs#9947
  • Loading branch information
nabijaczleweli committed Apr 27, 2021
1 parent b0269cd commit d54257a
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 4 deletions.
63 changes: 63 additions & 0 deletions config/user-libfetch.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
dnl #
dnl # Check for a libfetch - either fetch(3) or libcurl, to back keylocation=https://.
dnl #
dnl # There are two configuration dimensions:
dnl # * fetch(3) vs libcurl
dnl # * static vs dynamic
dnl #
dnl # fetch(3) is only ever dynamic, i.e. we load it via dlopen(3) by the "libfetch.so" soname.
dnl #
dnl # libcurl development packages include curl-config(1) – we want:
dnl # * HTTPS support
dnl # * major version 7 (i.e. "from this century"), 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="-ldl"
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 <fetch.h>], [fetchGetHTTP("", "");])], [
have_libfetch=1
LIBFETCH_IS_FETCH=1
LIBFETCH_SONAME='"libfetch.so"'
LIBFETCH_DYNAMIC=1
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 "$((0x070000))" &&
test "$(("0x$(curl-config --vernum)"))" -lt "$((0x080000))"; then
have_libfetch=1
LIBFETCH_IS_LIBCURL=1
if false && test "$(curl-config --built-shared)" = "yes"; then
LIBFETCH_DYNAMIC=1
LIBFETCH_SONAME='"libcurl.so.4"'
else
LIBFETCH_LIBS="$(curl-config --libs)"
fi
CCFLAGS="$CCFLAGS $(curl-config --cflags)"
AC_MSG_RESULT([libcurl])
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
1 change: 1 addition & 0 deletions include/libzfs_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ struct libzfs_handle {
boolean_t libzfs_prop_debug;
regex_t libzfs_urire;
uint64_t libzfs_max_nvlist;
void *libfetch;
};

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
130 changes: 130 additions & 0 deletions lib/libzfs/libzfs_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
#include <signal.h>
#include <errno.h>
#include <openssl/evp.h>
#if LIBFETCH_DYNAMIC
#include <dlfcn.h>
#endif
#if LIBFETCH_IS_FETCH
#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 +67,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 +494,125 @@ 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;

if (hdl->libfetch == (void *)-1) {
ret = ENOSYS;
goto end;
}

boolean_t ok;
#if 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);
ok = curl_easy_init && curl_easy_setopt && curl_easy_perform &&
curl_easy_cleanup && curl_easy_strerror;
#elif LIBFETCH_IS_FETCH
LOAD_FUNCTION(fetchGetHTTP);
char *fetchLastErrString = dlsym(hdl->libfetch, "fetchLastErrString");
ok = fetchGetHTTP && fetchLastErrString;
#endif
if (!ok) {
ret = ENOSYS;
goto end;
}
#endif

#if 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;
goto end;
}

int kfd = mkostemps(path, strlen(".https"), O_CLOEXEC);
if (kfd == -1) {
free(path);
ret = errno;
goto end;
}

if ((key = fdopen(kfd, "r+")) == NULL) {
free(path);
close(kfd);
ret = errno;
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);

curl_easy_cleanup(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 {
rewind(key);
}
#elif LIBFETCH_IS_FETCH
key = fetchGetHTTP(uri, "");
if (key == NULL) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"Failed to connect to %s: %s"),
uri, fetchLastErrString);
ret = ENETDOWN;
}
#else
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
7 changes: 7 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,10 @@ 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);
#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

0 comments on commit d54257a

Please sign in to comment.