diff --git a/config/user-libfetch.m4 b/config/user-libfetch.m4 new file mode 100644 index 000000000000..73e340a475f2 --- /dev/null +++ b/config/user-libfetch.m4 @@ -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 ], [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]) +]) diff --git a/config/user.m4 b/config/user.m4 index e799faffb61c..670820b37715 100644 --- a/config/user.m4 +++ b/config/user.m4 @@ -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 diff --git a/include/libzfs_impl.h b/include/libzfs_impl.h index 4f44909bf22c..8c214a682644 100644 --- a/include/libzfs_impl.h +++ b/include/libzfs_impl.h @@ -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 { diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index 1a7698b4760e..31267fd9a5e9 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -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 diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c index 5fb93d265965..be8dbedceb43 100644 --- a/lib/libzfs/libzfs_crypto.c +++ b/lib/libzfs/libzfs_crypto.c @@ -26,6 +26,14 @@ #include #include #include +#if LIBFETCH_DYNAMIC +#include +#endif +#if LIBFETCH_IS_FETCH +#include +#elif LIBFETCH_IS_LIBCURL +#include +#endif #include #include "libzfs_impl.h" #include "zfeature_common.h" @@ -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 } }; @@ -483,6 +494,120 @@ 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) + return (EINVAL); + +#if LIBFETCH_DYNAMIC + 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 + __typeof__(curl_easy_init) *curl_easy_init = dlsym(hdl->libfetch, "curl_easy_init"); + __typeof__(curl_easy_setopt) *curl_easy_setopt = dlsym(hdl->libfetch, "curl_easy_setopt"); + __typeof__(curl_easy_perform) *curl_easy_perform = dlsym(hdl->libfetch, "curl_easy_perform"); + __typeof__(curl_easy_cleanup) *curl_easy_cleanup = dlsym(hdl->libfetch, "curl_easy_cleanup"); + __typeof__(curl_easy_strerror) *curl_easy_strerror = dlsym(hdl->libfetch, "curl_easy_strerror"); + ok = curl_easy_init && curl_easy_setopt && curl_easy_perform && + curl_easy_cleanup && curl_easy_strerror; +#elif LIBFETCH_IS_FETCH + __typeof__(fetchGetHTTP) *fetchGetHTTP = dlsym(hdl->libfetch, "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) { + 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 diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index 8038f7fa343e..3aa36fd644a2 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -44,6 +44,9 @@ #include #include #include +#if LIBFETCH_DYNAMIC +#include +#endif #include #include #include @@ -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); } diff --git a/man/man8/zfsprops.8 b/man/man8/zfsprops.8 index ec7d5d27873b..fa8d2d4d67b7 100644 --- a/man/man8/zfsprops.8 +++ b/man/man8/zfsprops.8 @@ -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 +.Sy keylocation Ns = Ns Sy prompt Ns | Ns Sy file:// Ns Em Ns | Ns Sy https:// Ns Em
.Xc Controls where the user's encryption key will be loaded from by default for commands such as @@ -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 diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 402d749c1aeb..4c50feadbc43 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -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 | ", "KEYLOCATION"); + "prompt | | ", "KEYLOCATION"); zprop_register_string(ZFS_PROP_REDACT_SNAPS, "redact_snaps", NULL, PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "[,...]", @@ -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); }