Skip to content

Commit b9cb6a5

Browse files
libzfs: add keylocation=https://, backed by fetch(3) or libcurl
Signed-off-by: Ahelenia Ziemiańska <[email protected]> Ref: #9543 Closes #9947
1 parent b0269cd commit b9cb6a5

File tree

9 files changed

+7032
-4246
lines changed

9 files changed

+7032
-4246
lines changed

config/user-libfetch.m4

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
dnl #
2+
dnl # Check for a libfetch - either fetch(3) or libcurl.
3+
dnl #
4+
dnl # There are two configuration dimensions:
5+
dnl # * fetch(3) vs libcurl
6+
dnl # * static vs dynamic
7+
dnl #
8+
dnl # fetch(3) is only dynamic.
9+
dnl #
10+
dnl # libcurl development packages include curl-config(1) – we want:
11+
dnl # * HTTPS support
12+
dnl # * version at least 7.10 ("October 2006"), for sover 4
13+
dnl # * to decide if it's static or not
14+
dnl #
15+
AC_DEFUN([ZFS_AC_CONFIG_USER_LIBFETCH], [
16+
AC_MSG_CHECKING([for libfetch])
17+
LIBFETCH_LIBS=
18+
LIBFETCH_IS_FETCH=0
19+
LIBFETCH_IS_LIBCURL=0
20+
LIBFETCH_DYNAMIC=0
21+
LIBFETCH_SONAME=
22+
have_libfetch=
23+
24+
saved_libs="$LIBS"
25+
LIBS="$LIBS -lfetch"
26+
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <fetch.h>], [fetchGetHTTP("", "");])], [
27+
have_libfetch=1
28+
LIBFETCH_IS_FETCH=1
29+
LIBFETCH_DYNAMIC=1
30+
LIBFETCH_SONAME='"libfetch.so"'
31+
LIBFETCH_LIBS="-ldl"
32+
AC_MSG_RESULT([fetch(3)])
33+
], [])
34+
LIBS="$saved_libs"
35+
36+
if test -z "$have_libfetch"; then
37+
if curl-config --protocols 2>/dev/null | grep -q HTTPS &&
38+
test "$(("0x$(curl-config --vernum)"))" -ge "$((0x071000))"; then
39+
have_libfetch=1
40+
LIBFETCH_IS_LIBCURL=1
41+
if test "$(curl-config --built-shared)" = "yes"; then
42+
LIBFETCH_DYNAMIC=1
43+
LIBFETCH_SONAME='"libcurl.so.4"'
44+
LIBFETCH_LIBS="-ldl"
45+
AC_MSG_RESULT([libcurl])
46+
else
47+
LIBFETCH_LIBS="$(curl-config --libs)"
48+
AC_MSG_RESULT([libcurl (static)])
49+
fi
50+
51+
CCFLAGS="$CCFLAGS $(curl-config --cflags)"
52+
53+
fi
54+
fi
55+
56+
if test -z "$have_libfetch"; then
57+
AC_MSG_RESULT([none])
58+
fi
59+
60+
AC_SUBST([LIBFETCH_LIBS])
61+
AC_DEFINE_UNQUOTED([LIBFETCH_IS_FETCH], [$LIBFETCH_IS_FETCH], [libfetch is fetch(3)])
62+
AC_DEFINE_UNQUOTED([LIBFETCH_IS_LIBCURL], [$LIBFETCH_IS_LIBCURL], [libfetch is libcurl])
63+
AC_DEFINE_UNQUOTED([LIBFETCH_DYNAMIC], [$LIBFETCH_DYNAMIC], [whether the chosen libfetch is to be loaded at run-time])
64+
AC_DEFINE_UNQUOTED([LIBFETCH_SONAME], [$LIBFETCH_SONAME], [soname of chosen libfetch])
65+
])

config/user.m4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [
2222
ZFS_AC_CONFIG_USER_LIBCRYPTO
2323
ZFS_AC_CONFIG_USER_LIBAIO
2424
ZFS_AC_CONFIG_USER_LIBATOMIC
25+
ZFS_AC_CONFIG_USER_LIBFETCH
2526
ZFS_AC_CONFIG_USER_CLOCK_GETTIME
2627
ZFS_AC_CONFIG_USER_PAM
2728
ZFS_AC_CONFIG_USER_RUNSTATEDIR

include/libzfs_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ struct libzfs_handle {
6969
boolean_t libzfs_prop_debug;
7070
regex_t libzfs_urire;
7171
uint64_t libzfs_max_nvlist;
72+
void *libfetch;
73+
char *libfetch_load_error;
7274
};
7375

7476
struct zfs_handle {

lib/libzfs/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ libzfs_la_LIBADD = \
7575
$(abs_top_builddir)/lib/libnvpair/libnvpair.la \
7676
$(abs_top_builddir)/lib/libuutil/libuutil.la
7777

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

8080
libzfs_la_LDFLAGS = -pthread
8181

lib/libzfs/libzfs.abi

Lines changed: 6800 additions & 4242 deletions
Large diffs are not rendered by default.

lib/libzfs/libzfs_crypto.c

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
#include <signal.h>
2727
#include <errno.h>
2828
#include <openssl/evp.h>
29+
#if LIBFETCH_DYNAMIC
30+
#include <dlfcn.h>
31+
#endif
32+
#if LIBFETCH_IS_FETCH
33+
#include <fetch.h>
34+
#elif LIBFETCH_IS_LIBCURL
35+
#include <curl/curl.h>
36+
#endif
2937
#include <libzfs.h>
3038
#include "libzfs_impl.h"
3139
#include "zfeature_common.h"
@@ -59,9 +67,12 @@ static int caught_interrupt;
5967

6068
static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
6169
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
70+
static int get_key_material_https(libzfs_handle_t *, const char *, const char *,
71+
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
6272

6373
static zfs_uri_handler_t uri_handlers[] = {
6474
{ "file", get_key_material_file },
75+
{ "https", get_key_material_https },
6576
{ NULL, NULL }
6677
};
6778

@@ -483,6 +494,141 @@ get_key_material_file(libzfs_handle_t *hdl, const char *uri,
483494
return (ret);
484495
}
485496

497+
static int
498+
get_key_material_https(libzfs_handle_t *hdl, const char *uri,
499+
const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
500+
uint8_t **restrict buf, size_t *restrict len_out)
501+
{
502+
int ret = 0;
503+
FILE *key = NULL;
504+
505+
if (strlen(uri) < 8) {
506+
ret = EINVAL;
507+
goto end;
508+
}
509+
510+
#if LIBFETCH_DYNAMIC
511+
#define LOAD_FUNCTION(func) \
512+
__typeof__(func) *func = dlsym(hdl->libfetch, #func);
513+
514+
if (hdl->libfetch == NULL)
515+
hdl->libfetch = dlopen(LIBFETCH_SONAME, RTLD_LAZY);
516+
517+
if (hdl->libfetch == NULL) {
518+
hdl->libfetch = (void *)-1;
519+
hdl->libfetch_load_error = strdup(dlerror() ?: "(?)");
520+
}
521+
522+
if (hdl->libfetch == (void *)-1) {
523+
ret = ENOSYS;
524+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
525+
"Couldn't load %s: %s"),
526+
LIBFETCH_SONAME, hdl->libfetch_load_error ?: "(?)");
527+
goto end;
528+
}
529+
530+
boolean_t ok;
531+
#if LIBFETCH_IS_FETCH
532+
LOAD_FUNCTION(fetchGetHTTP);
533+
char *fetchLastErrString = dlsym(hdl->libfetch, "fetchLastErrString");
534+
535+
ok = fetchGetHTTP && fetchLastErrString;
536+
#elif LIBFETCH_IS_LIBCURL
537+
LOAD_FUNCTION(curl_easy_init);
538+
LOAD_FUNCTION(curl_easy_setopt);
539+
LOAD_FUNCTION(curl_easy_perform);
540+
LOAD_FUNCTION(curl_easy_cleanup);
541+
LOAD_FUNCTION(curl_easy_strerror);
542+
543+
ok = curl_easy_init && curl_easy_setopt && curl_easy_perform &&
544+
curl_easy_cleanup && curl_easy_strerror;
545+
#endif
546+
if (!ok) {
547+
ret = ENOSYS;
548+
goto end;
549+
}
550+
#endif
551+
552+
#if LIBFETCH_IS_FETCH
553+
key = fetchGetHTTP(uri, "");
554+
if (key == NULL) {
555+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
556+
"Failed to connect to %s: %s"),
557+
uri, fetchLastErrString);
558+
ret = ENETDOWN;
559+
}
560+
#elif LIBFETCH_IS_LIBCURL
561+
CURL *curl = curl_easy_init();
562+
if (curl == NULL) {
563+
ret = ENOTSUP;
564+
goto end;
565+
}
566+
567+
char *path;
568+
if (asprintf(&path,
569+
"%s/libzfs-XXXXXXXX.https", getenv("TMPDIR") ?: "/tmp") == -1) {
570+
ret = ENOMEM;
571+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s"),
572+
strerror(ret));
573+
goto end;
574+
}
575+
576+
int kfd = mkostemps(path, strlen(".https"), O_CLOEXEC);
577+
if (kfd == -1) {
578+
ret = errno;
579+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
580+
"Couldn't create temporary file %s: %s"),
581+
path, strerror(ret));
582+
free(path);
583+
goto end;
584+
}
585+
586+
if ((key = fdopen(kfd, "r+")) == NULL) {
587+
ret = errno;
588+
free(path);
589+
(void) close(kfd);
590+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
591+
"Couldn't reopen temporary file: %s"), strerror(ret));
592+
goto end;
593+
}
594+
(void) unlink(path);
595+
free(path);
596+
597+
char errbuf[CURL_ERROR_SIZE] = "";
598+
(void) curl_easy_setopt(curl, CURLOPT_URL, uri);
599+
(void) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
600+
(void) curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 30000L);
601+
(void) curl_easy_setopt(curl, CURLOPT_WRITEDATA, key);
602+
(void) curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
603+
604+
CURLcode res = curl_easy_perform(curl);
605+
606+
curl_easy_cleanup(curl);
607+
608+
if (res != CURLE_OK) {
609+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
610+
"Failed to connect to %s: %s"),
611+
uri, strlen(errbuf) ? errbuf : curl_easy_strerror(res));
612+
ret = ENETDOWN;
613+
} else {
614+
rewind(key);
615+
}
616+
#else
617+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
618+
"No heylocation=https:// back-end."));
619+
ret = ENOSYS;
620+
#endif
621+
622+
end:
623+
if (ret == 0)
624+
ret = get_key_material_raw(key, keyformat, buf, len_out);
625+
626+
if (key != NULL)
627+
fclose(key);
628+
629+
return (ret);
630+
}
631+
486632
/*
487633
* Attempts to fetch key material, no matter where it might live. The key
488634
* material is allocated and returned in km_out. *can_retry_out will be set

lib/libzfs/libzfs_util.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
#include <strings.h>
4545
#include <unistd.h>
4646
#include <math.h>
47+
#if LIBFETCH_DYNAMIC
48+
#include <dlfcn.h>
49+
#endif
4750
#include <sys/stat.h>
4851
#include <sys/mnttab.h>
4952
#include <sys/mntent.h>
@@ -1081,6 +1084,11 @@ libzfs_fini(libzfs_handle_t *hdl)
10811084
libzfs_core_fini();
10821085
regfree(&hdl->libzfs_urire);
10831086
fletcher_4_fini();
1087+
#if LIBFETCH_DYNAMIC
1088+
if (hdl->libfetch != (void *)-1 && hdl->libfetch != NULL)
1089+
(void) dlclose(hdl->libfetch);
1090+
free(hdl->libfetch_load_error);
1091+
#endif
10841092
free(hdl);
10851093
}
10861094

man/man8/zfsprops.8

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@ encryption suite cannot be changed after dataset creation, the keyformat can be
10851085
with
10861086
.Nm zfs Cm change-key .
10871087
.It Xo
1088-
.Sy keylocation Ns = Ns Sy prompt Ns | Ns Sy file:// Ns Em </absolute/file/path>
1088+
.Sy keylocation Ns = Ns Sy prompt Ns | Ns Sy file:// Ns Em </absolute/file/path> Ns | Ns Sy https:// Ns Em <address>
10891089
.Xc
10901090
Controls where the user's encryption key will be loaded from by default for
10911091
commands such as
@@ -1109,7 +1109,11 @@ to access the encrypted data (see
11091109
for details). This setting will also allow the key to be passed in via STDIN,
11101110
but users should be careful not to place keys which should be kept secret on
11111111
the command line. If a file URI is selected, the key will be loaded from the
1112-
specified absolute file path.
1112+
specified absolute file path. If an HTTPS URI is selected, it will be GETted
1113+
using
1114+
.Xr fetch 3 ,
1115+
libcurl, or nothing, depending on compile-time configuration and run-time
1116+
availability. All back-ends support HTTP Basic auth encoded in the URL.
11131117
.It Sy pbkdf2iters Ns = Ns Ar iterations
11141118
Controls the number of PBKDF2 iterations that a
11151119
.Sy passphrase

module/zcommon/zfs_prop.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ zfs_prop_init(void)
583583
"ENCROOT");
584584
zprop_register_string(ZFS_PROP_KEYLOCATION, "keylocation",
585585
"none", PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
586-
"prompt | <file URI>", "KEYLOCATION");
586+
"prompt | <file URI> | <https URL>", "KEYLOCATION");
587587
zprop_register_string(ZFS_PROP_REDACT_SNAPS,
588588
"redact_snaps", NULL, PROP_READONLY,
589589
ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "<snapshot>[,...]",
@@ -936,6 +936,8 @@ zfs_prop_valid_keylocation(const char *str, boolean_t encrypted)
936936
return (B_TRUE);
937937
else if (strlen(str) > 8 && strncmp("file:///", str, 8) == 0)
938938
return (B_TRUE);
939+
else if (strlen(str) > 8 && strncmp("https://", str, 8) == 0)
940+
return (B_TRUE);
939941

940942
return (B_FALSE);
941943
}

0 commit comments

Comments
 (0)