diff --git a/.github/workflows/postgresql-16-pgdg-package-pgxs.yml b/.github/workflows/postgresql-16-pgdg-package-pgxs.yml index 04190ae510c..41456390b36 100644 --- a/.github/workflows/postgresql-16-pgdg-package-pgxs.yml +++ b/.github/workflows/postgresql-16-pgdg-package-pgxs.yml @@ -24,7 +24,7 @@ jobs: run: | sudo apt-get install -y libreadline6-dev systemtap-sdt-dev wget \ zlib1g-dev libssl-dev libpam0g-dev bison flex libipc-run-perl \ - libjson-c-dev + libjson-c-dev libcurl4-openssl-dev sudo /usr/bin/perl -MCPAN -e 'install IPC::RUN' sudo /usr/bin/perl -MCPAN -e 'install Text::Trim' diff --git a/.github/workflows/postgresql-16-src-make.yml b/.github/workflows/postgresql-16-src-make.yml index 03dc2527de2..6211610a660 100644 --- a/.github/workflows/postgresql-16-src-make.yml +++ b/.github/workflows/postgresql-16-src-make.yml @@ -26,7 +26,7 @@ jobs: libxml2-dev libxslt-dev xsltproc libkrb5-dev libldap2-dev \ libsystemd-dev gettext tcl-dev libperl-dev pkg-config clang-11 \ llvm-11 llvm-11-dev libselinux1-dev python3-dev \ - uuid-dev liblz4-dev libjson-c-dev + uuid-dev liblz4-dev libjson-c-dev libcurl4-openssl-dev sudo /usr/bin/perl -MCPAN -e 'install IPC::RUN' sudo /usr/bin/perl -MCPAN -e 'install Text::Trim' diff --git a/.github/workflows/postgresql-16-src-meson-perf.yml b/.github/workflows/postgresql-16-src-meson-perf.yml index 4284ca04be8..8d499e4ce7a 100644 --- a/.github/workflows/postgresql-16-src-meson-perf.yml +++ b/.github/workflows/postgresql-16-src-meson-perf.yml @@ -30,7 +30,7 @@ jobs: libsystemd-dev gettext tcl-dev libperl-dev pkg-config clang-11 \ llvm-11 llvm-11-dev libselinux1-dev python3-dev \ uuid-dev liblz4-dev meson ninja-build libjson-c-dev \ - sysbench + sysbench libcurl4-openssl-dev sudo /usr/bin/perl -MCPAN -e 'install IPC::RUN' sudo /usr/bin/perl -MCPAN -e 'install Text::Trim' diff --git a/.github/workflows/postgresql-16-src-meson.yml b/.github/workflows/postgresql-16-src-meson.yml index b36cb2abfb2..e8efd1e58a0 100644 --- a/.github/workflows/postgresql-16-src-meson.yml +++ b/.github/workflows/postgresql-16-src-meson.yml @@ -26,9 +26,13 @@ jobs: libxml2-dev libxslt-dev xsltproc libkrb5-dev libldap2-dev \ libsystemd-dev gettext tcl-dev libperl-dev pkg-config clang-11 \ llvm-11 llvm-11-dev libselinux1-dev python3-dev \ - uuid-dev liblz4-dev meson ninja-build libjson-c-dev + uuid-dev liblz4-dev meson ninja-build libjson-c-dev \ + gpg wget libcurl4-openssl-dev sudo /usr/bin/perl -MCPAN -e 'install IPC::RUN' sudo /usr/bin/perl -MCPAN -e 'install Text::Trim' + wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list + sudo apt update && sudo apt install -y vault - name: Clone postgres repository uses: actions/checkout@v2 @@ -52,7 +56,7 @@ jobs: cd build && ninja && ninja install working-directory: src - - name: Test postgres-tde-ext + - name: Test postgres-tde-ext with keyring_file run: | cp ../contrib/postgres-tde-ext/keyring.json /tmp/keyring.json meson test --suite setup -v @@ -68,3 +72,26 @@ jobs: src/build/testrun/postgres-tde-ext/regress/ retention-days: 3 + - name: Test postgres-tde-ext with keyring_vault + run: | + TV=$(mktemp) + { exec >$TV; vault server -dev; } & + sleep 10 + ROOT_TOKEN=$(cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n) + echo "Root token: $ROOT_TOKEN" + cp ../contrib/postgres-tde-ext/keyring-vault.json /tmp/keyring.json + sed -i "s/ROOT_TOKEN/$ROOT_TOKEN/g" /tmp/keyring.json + cat /tmp/keyring.json + meson test --suite setup -v + meson test --suite postgres-tde-ext -v --num-processes 1 + working-directory: src/build + + - name: Report on test fail + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: Regressions diff and postgresql log + path: | + src/build/testrun/postgres-tde-ext/regress/ + retention-days: 3 + diff --git a/Makefile.in b/Makefile.in index ac2df180855..56016e6a94c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -32,6 +32,7 @@ src/access/pg_tde_ddl.o \ src/transam/pg_tde_xact_handler.o \ src/keyring/keyring_config.o \ src/keyring/keyring_file.o \ +src/keyring/keyring_vault.o \ src/keyring/keyring_api.o \ src/pg_tde.o @@ -50,4 +51,4 @@ include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif -override SHLIB_LINK += @tde_LDFLAGS@ -lcrypto -lssl +override SHLIB_LINK += @tde_LDFLAGS@ -lcrypto -lssl -lcurl diff --git a/configure b/configure index b5290bdf2c8..2a9e0851c2c 100755 --- a/configure +++ b/configure @@ -710,6 +710,7 @@ ac_subst_files='' ac_user_opts=' enable_option_checking with_jsonc +with_libcurl ' ac_precious_vars='build_alias host_alias @@ -1341,6 +1342,7 @@ Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-jsonc= Location where json_object.h is installed + --with-libcurl= Location where curl/curl.h is installed Some influential environment variables: CC C compiler command @@ -2407,7 +2409,7 @@ case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac -# REQUIRE_LIB(name,lib,testfn,description) +# REQUIRE_LIB(name,lib,testfn,test_include.h) # name = The complete name of the library file without the extension. # lib = The name of the library file without the 'lib' prefix and without the extension. # testfn = One function included in the library that can be used for a test compilation. @@ -3531,6 +3533,96 @@ else $as_nop fi +} + { + +# Check whether --with-libcurl was given. +if test ${with_libcurl+y} +then : + withval=$with_libcurl; +else $as_nop + with_libcurl=default +fi + + if test "x$with_libcurl" == xdefault +then : + + case $host_os in + darwin*) libpathx=($HOMEBREW_CELLAR/curl/*) + tde_CPPFLAGS="$tde_CPPFLAGS -I$libpathx/include/curl" + tde_LDFLAGS="$tde_LDFLAGS -L$libpathx/lib -lcurl" ;; + *) tde_CPPFLAGS="$tde_CPPFLAGS -I/usr/include/curl" + tde_LDFLAGS="$tde_LDFLAGS -lcurl" ;; + esac + +else $as_nop + #AS_ELSE + tde_CPPFLAGS="$tde_CPPFLAGS -I${with_libcurl}/include" + tde_LDFLAGS="$tde_LDFLAGS -L${with_libcurl}/lib -lcurl" + +fi + + LDFLAGS="$LDFLAGS $tde_LDFLAGS" + CPPFLAGS="$CPPFLAGS $tde_CPPFLAGS" + + ac_fn_c_check_header_compile "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default" +if test "x$ac_cv_header_curl_curl_h" = xyes +then : + +else $as_nop + + as_fn_error $? "header file is required, try specifying --with-libcurl" "$LINENO" 5 + +fi + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for curl_easy_setopt in -lcurl" >&5 +printf %s "checking for curl_easy_setopt in -lcurl... " >&6; } +if test ${ac_cv_lib_curl_curl_easy_setopt+y} +then : + printf %s "(cached) " >&6 +else $as_nop + ac_check_lib_save_LIBS=$LIBS +LIBS="-lcurl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +char curl_easy_setopt (); +int +main (void) +{ +return curl_easy_setopt (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_curl_curl_easy_setopt=yes +else $as_nop + ac_cv_lib_curl_curl_easy_setopt=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_easy_setopt" >&5 +printf "%s\n" "$ac_cv_lib_curl_curl_easy_setopt" >&6; } +if test "x$ac_cv_lib_curl_curl_easy_setopt" = xyes +then : + printf "%s\n" "#define HAVE_LIBCURL 1" >>confdefs.h + + LIBS="-lcurl $LIBS" + +else $as_nop + + as_fn_error $? "curl was not found, try specifying --with-libcurl" "$LINENO" 5 + +fi + } @@ -4689,3 +4781,4 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi + diff --git a/configure.ac b/configure.ac index da52e5d96d3..79c8d376f20 100644 --- a/configure.ac +++ b/configure.ac @@ -44,10 +44,11 @@ AC_ARG_WITH([$1], AS_HELP_STRING([--with-$1=],[Location where $4 is instal #======================================= REQUIRE_LIB(jsonc, json-c, json_object_get, json_object.h) +REQUIRE_LIB(libcurl, curl, curl_easy_setopt, curl/curl.h) AC_SUBST(tde_CPPFLAGS) AC_SUBST(tde_LDFLAGS) AC_CONFIG_FILES([Makefile]) -AC_OUTPUT \ No newline at end of file +AC_OUTPUT diff --git a/keyring-vault.json b/keyring-vault.json new file mode 100644 index 00000000000..e4123aca1d6 --- /dev/null +++ b/keyring-vault.json @@ -0,0 +1,6 @@ +{ + 'provider': 'vault-v2', + 'token': 'ROOT_TOKEN', + 'url': 'http://127.0.0.1:8200', + 'mountPath': 'secret' +} diff --git a/meson.build b/meson.build index 68ba64a0540..94ebd482581 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,7 @@ # libjson-c-dev on ubuntu jsondep = dependency('json-c') +curldep = dependency('libcurl') pg_tde_sources = files( 'src/pg_tde.c', @@ -22,6 +23,7 @@ pg_tde_sources = files( 'src/keyring/keyring_config.c', 'src/keyring/keyring_file.c', + 'src/keyring/keyring_vault.c', 'src/keyring/keyring_api.c', 'src/pg_tde.c', @@ -29,7 +31,7 @@ pg_tde_sources = files( incdir = include_directories('src/include') -deps_update = {'dependencies': contrib_mod_args.get('dependencies') + [jsondep]} +deps_update = {'dependencies': contrib_mod_args.get('dependencies') + [jsondep, curldep]} mod_args = contrib_mod_args + deps_update diff --git a/src/include/keyring/keyring_config.h b/src/include/keyring/keyring_config.h index 4b5747e099c..bc70aaae380 100644 --- a/src/include/keyring/keyring_config.h +++ b/src/include/keyring/keyring_config.h @@ -6,6 +6,14 @@ #include +enum KeyringProvider +{ + PROVIDER_UNKNOWN, + PROVIDER_FILE, + PROVIDER_VAULT_V2, +} ; + +extern enum KeyringProvider keyringProvider; extern char* keyringConfigFile; extern char* keyringKeyPrefix; diff --git a/src/include/keyring/keyring_vault.h b/src/include/keyring/keyring_vault.h new file mode 100644 index 00000000000..bde773b1569 --- /dev/null +++ b/src/include/keyring/keyring_vault.h @@ -0,0 +1,19 @@ + +#ifndef KEYRING_VAULT_H +#define KEYRING_VAULT_H + +#include "postgres.h" + +#include + +#include "keyring_api.h" + +int keyringVaultPreloadCache(void); + +int keyringVaultParseConfiguration(json_object* configRoot); + +int keyringVaultStoreKey(const keyInfo* ki); + +int keyringVaultGetKey(keyName name, keyData* outData); + +#endif // KEYRING_FILE_H diff --git a/src/keyring/keyring_api.c b/src/keyring/keyring_api.c index 4cfef91e5c7..44f1b1f5799 100644 --- a/src/keyring/keyring_api.c +++ b/src/keyring/keyring_api.c @@ -1,6 +1,7 @@ #include "keyring/keyring_api.h" #include "keyring/keyring_file.h" +#include "keyring/keyring_vault.h" #include "keyring/keyring_config.h" #include "postgres.h" @@ -29,8 +30,18 @@ void keyringInitCache(void) memset(cache, 0, keyringCacheMemorySize()); } - // TODO: HARDCODED FILE PROVIDER - keyringFilePreloadCache(); + switch(keyringProvider) + { + case PROVIDER_FILE: + keyringFilePreloadCache(); + break; + case PROVIDER_VAULT_V2: + keyringVaultPreloadCache(); + break; + case PROVIDER_UNKNOWN: + // nop + break; + } } const keyInfo* keyringCacheStoreKey(keyName name, keyData data) @@ -57,7 +68,28 @@ const keyInfo* keyringGetKey(keyName name) return &cache->keys[i]; } } - // TODO: HARDCODED FILE PROVIDER + // not found in cache, try to look up + switch(keyringProvider) + { + case PROVIDER_FILE: + // nop, not implmeneted + break; + case PROVIDER_VAULT_V2: + { + keyData data; + data.len = 0; + keyringVaultGetKey(name, &data); + if(data.len > 0) + { + return keyringCacheStoreKey(name, data); + } + break; + } + case PROVIDER_UNKNOWN: + // nop + break; + } + #if KEYRING_DEBUG fprintf(stderr, " -- not found\n"); #endif @@ -66,18 +98,27 @@ const keyInfo* keyringGetKey(keyName name) const keyInfo* keyringStoreKey(keyName name, keyData data) { - // TODO: HARDCODED FILE PROVIDER - // Todo: we should first call the provider, and if it succeeds, add the key to the cache - // But as the current file implementation just dumps the cache to disk, this is a good first prototype const keyInfo* ki = keyringCacheStoreKey(name, data); #if KEYRING_DEBUG fprintf(stderr, "Storing key: %s\n", name.name); #endif - if(!keyringFileStoreKey(ki)) + switch(keyringProvider) { - return NULL; + case PROVIDER_FILE: + if(keyringFileStoreKey(ki)) return ki; + break; + case PROVIDER_VAULT_V2: + if(keyringVaultStoreKey(ki)) return ki; + break; + case PROVIDER_UNKNOWN: + // nop + break; } - return ki; + + // if we are here, storeKey failed, remove from cache + cache->keyCount--; + + return NULL; } keyName keyringConstructKeyName(const char* internalName, unsigned version) diff --git a/src/keyring/keyring_config.c b/src/keyring/keyring_config.c index 1a19038f29f..256304b6a4f 100644 --- a/src/keyring/keyring_config.c +++ b/src/keyring/keyring_config.c @@ -1,6 +1,7 @@ #include "keyring/keyring_config.h" #include "keyring/keyring_file.h" +#include "keyring/keyring_vault.h" #include #include @@ -11,6 +12,7 @@ char* keyringConfigFile = ""; char* keyringKeyPrefix = ""; +enum KeyringProvider keyringProvider = PROVIDER_UNKNOWN; static bool keyringCheckKeyPrefix(char **newval, void **extra, GucSource source) { @@ -116,12 +118,30 @@ bool keyringLoadConfiguration(const char* configFileName) goto cleanup; } - if(strncmp("file", provider, 5) != 0) + + if(strncmp("file", provider, 5) == 0) + { + ret = keyringFileParseConfiguration(root); + if(ret) + { + keyringProvider = PROVIDER_FILE; + } + } + + if(strncmp("vault-v2", provider, 9) == 0) + { + ret = keyringVaultParseConfiguration(root); + if(ret) + { + keyringProvider = PROVIDER_VAULT_V2; + } + } + + if(keyringProvider == PROVIDER_UNKNOWN) { - elog(ERROR, "Invalid pg_tde.keyringConfigFile: Unknown 'provider': %s. Currently only 'file' provider is supported. Keyring is not available.", provider); + elog(ERROR, "Invalid pg_tde.keyringConfigFile: Unknown 'provider': %s. Currently only 'file' and 'vault-v2', providers are supported. Keyring is not available.", provider); } - ret = keyringFileParseConfiguration(root); if (!ret) { @@ -138,7 +158,7 @@ const char* keyringParseStringParam(json_object* object) { if(json_object_get_type(object) == json_type_object) { - elog(WARNING, "Remote parameters are not yet implementeed"); + elog(WARNING, "Remote parameters are not yet implemented"); } return json_object_get_string(object); diff --git a/src/keyring/keyring_file.c b/src/keyring/keyring_file.c index 322c75d411f..39943c17f34 100644 --- a/src/keyring/keyring_file.c +++ b/src/keyring/keyring_file.c @@ -52,13 +52,15 @@ int keyringFilePreloadCache(void) int keyringFileStoreKey(const keyInfo* ki) { + FILE *f; + if (strlen(keyringFileDataFileName) == 0) { elog(ERROR, "Keyring datafile is not set"); return false; } // First very basic prototype: we just dump the cache to disk - FILE* f = fopen(keyringFileDataFileName, "w"); + f = fopen(keyringFileDataFileName, "w"); if(f == NULL) { elog(ERROR, "Couldn't write keyring data into '%s'", keyringFileDataFileName); diff --git a/src/keyring/keyring_vault.c b/src/keyring/keyring_vault.c new file mode 100644 index 00000000000..114ab0f62e6 --- /dev/null +++ b/src/keyring/keyring_vault.c @@ -0,0 +1,301 @@ + +#include "keyring/keyring_vault.h" +#include "keyring/keyring_config.h" +#include "pg_tde_defines.h" + +#include +#include + +#include + +#include "common/base64.h" + +char keyringVaultToken[128]; +char keyringVaultUrl[128]; +char keyringVaultCaPath[256]; +char keyringVaultMountPath[128]; + +CURL* curl = NULL; +struct curl_slist *curlList = NULL; + +typedef struct curlString { + char *ptr; + size_t len; +} curlString; + +static size_t writefunc(void *ptr, size_t size, size_t nmemb, struct curlString *s) +{ + size_t new_len = s->len + size*nmemb; + s->ptr = repalloc(s->ptr, new_len+1); + if (s->ptr == NULL) { + exit(EXIT_FAILURE); + } + memcpy(s->ptr+s->len, ptr, size*nmemb); + s->ptr[new_len] = '\0'; + s->len = new_len; + + return size*nmemb; +} + +static bool curlSetupSession(const char* url, curlString* str) +{ + if(curl == NULL) + { + curl = curl_easy_init(); + + if(curl == NULL) return 0; + } + + if(curlList == NULL) + { + char tokenHeader[256]; + strcpy(tokenHeader, "X-Vault-Token:"); + strcat(tokenHeader, keyringVaultToken); + + curlList = curl_slist_append(curlList, tokenHeader); + if(curlList == NULL) return 0; + + curlList = curl_slist_append(curlList, "Content-Type: application/json"); + if(curlList == NULL) return 0; + } + + + if(curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curlList) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL) != CURLE_OK) return 0; + if(strlen(keyringVaultCaPath) > 0 && curl_easy_setopt( + curl, CURLOPT_CAINFO, keyringVaultCaPath) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,(long)CURL_HTTP_VERSION_1_1) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,writefunc) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_WRITEDATA,str) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK) return 0; + + return 1; +} + +static bool curlPerform(const char* url, curlString* outStr, long* httpCode, const char* postData) +{ +#if KEYRING_DEBUG + elog(DEBUG1, "Performing Vault HTTP [%s] request to '%s'", postData != NULL ? "POST" : "GET", url); + if(postData != NULL) + { + elog(DEBUG2, "Postdata: '%s'", postData); + } +#endif + outStr->ptr = palloc0(1); + outStr->len = 0; + + if(!curlSetupSession(url, outStr)) return 0; + + if(postData != NULL) + { + if(curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData) != CURLE_OK) return 0; + } else + { + if(curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL) != CURLE_OK) return 0; + if(curl_easy_setopt(curl, CURLOPT_POST, 0) != CURLE_OK) return 0; + } + + if(curl_easy_perform(curl) != CURLE_OK) return 0; + if(curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, httpCode) != CURLE_OK) return 0; + +#if KEYRING_DEBUG + elog(DEBUG2, "Vault response [%li] '%s'", *httpCode, outStr->ptr != NULL ? outStr->ptr : ""); +#endif + + return 1; +} + + +int keyringVaultPreloadCache(void) +{ + // nop + return 1; +} + +static bool keyringConfigExtractParameter(json_object* configRoot, const char* name, char* out, unsigned outMaxLen, bool optional) +{ + json_object* dataO; + const char* stringData; + + if(!json_object_object_get_ex(configRoot, name, &dataO)) + { + if(!optional) + { + elog(ERROR, "Missing '%s' attribute.", name); + } + return 0; + } + + stringData = keyringParseStringParam(dataO); + + if(stringData == NULL) + { + if(!optional) + { + elog(ERROR, "Couldn't parse '%s' attribute.", name); + } + return 0; + } + + if(strlen(stringData) > outMaxLen-1) + { + elog(WARNING, "Attribute '%s' is too long, maximum is %u, truncated.", name, outMaxLen); + } + + strncpy(out, stringData, outMaxLen-1); + + return 1; +} + +int keyringVaultParseConfiguration(json_object* configRoot) +{ + if(!keyringConfigExtractParameter(configRoot, "token", keyringVaultToken, 128, 0)) + { + return 0; + } + + if(!keyringConfigExtractParameter(configRoot, "url", keyringVaultUrl, 128, 0)) + { + return 0; + } + + if(!keyringConfigExtractParameter(configRoot, "mountPath", keyringVaultMountPath, 128, 0)) + { + return 0; + } + + keyringConfigExtractParameter(configRoot, "caPath", keyringVaultCaPath, 256, 1); + + return 1; +} + +static void keyringVaultKeyUrl(char* out, keyName name) +{ + strcpy(out, keyringVaultUrl); + strcat(out, "/v1/"); + strcat(out, keyringVaultMountPath); + strcat(out, "/data/"); + strcat(out, name.name); +} + +int keyringVaultStoreKey(const keyInfo* ki) +{ + char url[512]; + curlString str; + long httpCode = 0; + json_object *request = json_object_new_object(); + json_object *data = json_object_new_object(); + char keyData[64]; + int keyLen = 0; + + keyLen = pg_b64_encode((char*)ki->data.data, ki->data.len, keyData, 64); + keyData[keyLen] = 0; + json_object_object_add(data, "key", json_object_new_string(keyData)); + json_object_object_add(request, "data", data); + +#if KEYRING_DEBUG + elog(DEBUG1, "Sending base64 key: %s", keyData); +#endif + + keyringVaultKeyUrl(url, ki->name); + if(!curlPerform(url, &str, &httpCode, json_object_to_json_string(request))) + { + elog(ERROR, "HTTP(S) request to vault failed."); + if(str.ptr != NULL) pfree(str.ptr); + json_object_put(request); + return 0; + } + + json_object_put(request); + if(str.ptr != NULL) pfree(str.ptr); + + return httpCode / 100 == 2; +} + +int keyringVaultGetKey(keyName name, keyData* outData) +{ + char url[512]; + curlString str; + json_object *response = NULL; + long httpCode = 0; + int ret = 0; + + json_object *data = NULL; + json_object *data2 = NULL; + json_object *keyO = NULL; + const char *key = NULL; + + keyringVaultKeyUrl(url, name); + if(!curlPerform(url, &str, &httpCode, NULL)) + { + elog(ERROR, "HTTP(S) request to vault failed."); + goto cleanup; + } + + if(httpCode / 100 != 2) + { + if(httpCode != 404) + { + elog(ERROR, "Unexpected HTTP code: %li", httpCode); + } + goto cleanup; + } + + response = json_tokener_parse(str.ptr); + + if(response == NULL) + { + elog(ERROR, "Vault response is not a json object."); + goto cleanup; + } + + if(!json_object_object_get_ex(response, "data", &data)) + { + elog(ERROR, "No data attribute in Vault response."); + goto cleanup; + } + + if(!json_object_object_get_ex(data, "data", &data2)) + { + elog(ERROR, "No data.data attribute in Vault response."); + goto cleanup; + } + + if(!json_object_object_get_ex(data2, "key", &keyO)) + { + elog(ERROR, "No data.data.key attribute in Vault response."); + goto cleanup; + } + + key = json_object_get_string(keyO); + if(key == NULL || strlen(key) == 0) + { + elog(ERROR, "Key doesn't exist or empty"); + goto cleanup; + } + +#if KEYRING_DEBUG + elog(DEBUG1, "Retrieved base64 key: %s", key); +#endif + + outData->len = pg_b64_decode(key, strlen(key), (char*)outData->data, 32); + + if(outData->len != 32) + { + goto cleanup; + } + + ret = 1; + +cleanup: + if(str.ptr != NULL) pfree(str.ptr); + if(response != NULL) json_object_put(response); + return ret; +} + +