Skip to content

Commit abf2a5a

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 abf2a5a

24 files changed

+6817
-4266
lines changed

.github/workflows/zfs-tests-functional.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
xfslibs-dev libattr1-dev libacl1-dev libudev-dev libdevmapper-dev \
2727
libssl-dev libffi-dev libaio-dev libelf-dev libmount-dev \
2828
libpam0g-dev pamtester python-dev python-setuptools python-cffi \
29-
python3 python3-dev python3-setuptools python3-cffi
29+
python3 python3-dev python3-setuptools python3-cffi libcurl4-openssl-dev
3030
- name: Autogen.sh
3131
run: |
3232
sh autogen.sh

.github/workflows/zfs-tests-sanity.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
xfslibs-dev libattr1-dev libacl1-dev libudev-dev libdevmapper-dev \
2323
libssl-dev libffi-dev libaio-dev libelf-dev libmount-dev \
2424
libpam0g-dev pamtester python-dev python-setuptools python-cffi \
25-
python3 python3-dev python3-setuptools python3-cffi
25+
python3 python3-dev python3-setuptools python3-cffi libcurl4-openssl-dev
2626
- name: Autogen.sh
2727
run: |
2828
sh autogen.sh

config/user-libfetch.m4

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

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: 6450 additions & 4244 deletions
Large diffs are not rendered by default.

lib/libzfs/libzfs_crypto.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
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 <sys/param.h>
34+
#include <stdio.h>
35+
#include <fetch.h>
36+
#elif LIBFETCH_IS_LIBCURL
37+
#include <curl/curl.h>
38+
#endif
2939
#include <libzfs.h>
3040
#include "libzfs_impl.h"
3141
#include "zfeature_common.h"
@@ -59,9 +69,12 @@ static int caught_interrupt;
5969

6070
static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
6171
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
72+
static int get_key_material_https(libzfs_handle_t *, const char *, const char *,
73+
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
6274

6375
static zfs_uri_handler_t uri_handlers[] = {
6476
{ "file", get_key_material_file },
77+
{ "https", get_key_material_https },
6578
{ NULL, NULL }
6679
};
6780

@@ -483,6 +496,153 @@ get_key_material_file(libzfs_handle_t *hdl, const char *uri,
483496
return (ret);
484497
}
485498

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

tests/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ The pre-requisites for running the ZFS Test Suite are:
3939
* The ZFS Test Suite will add users and groups to test machine to
4040
verify functionality. Therefore it is strongly advised that a
4141
dedicated test machine, which can be a VM, be used for testing.
42+
* ZFS configured with a libfetch back-end and an internet connection.
4243

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

@@ -146,7 +147,7 @@ with the `zfs-tests.sh` wrapper script will look something like this:
146147
Results Summary
147148
SKIP 52
148149
PASS 1129
149-
150+
150151
Running Time: 02:35:33
151152
Percent passed: 95.6%
152153
Log directory: /var/tmp/test_results/20180515T054509

tests/runfiles/common.run

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ tags = ['functional', 'cli_root', 'zfs_inherit']
198198

199199
[tests/functional/cli_root/zfs_load-key]
200200
tests = ['zfs_load-key', 'zfs_load-key_all', 'zfs_load-key_file',
201-
'zfs_load-key_location', 'zfs_load-key_noop', 'zfs_load-key_recursive']
201+
'zfs_load-key_https', 'zfs_load-key_location', 'zfs_load-key_noop',
202+
'zfs_load-key_recursive']
202203
tags = ['functional', 'cli_root', 'zfs_load-key']
203204

204205
[tests/functional/cli_root/zfs_mount]

tests/runfiles/sanity.run

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ tags = ['functional', 'cli_root', 'zfs_inherit']
146146

147147
[tests/functional/cli_root/zfs_load-key]
148148
tests = ['zfs_load-key', 'zfs_load-key_all', 'zfs_load-key_file',
149-
'zfs_load-key_location', 'zfs_load-key_noop', 'zfs_load-key_recursive']
149+
'zfs_load-key_https', 'zfs_load-key_location', 'zfs_load-key_noop',
150+
'zfs_load-key_recursive']
150151
tags = ['functional', 'cli_root', 'zfs_load-key']
151152

152153
[tests/functional/cli_root/zfs_mount]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F

0 commit comments

Comments
 (0)