Skip to content

Commit 0a7d5d4

Browse files
author
Benno Evers
committed
Allow passing OpenSSL options as environment variables
CAF currently expects to receive OpenSSL certificates and keys as filenames. This matches the expectations of libopenssl, but is cumbersome to use in a Cloud context. In particular, ECS can only pass secrets as environment variables. So we extend the command-line option to also accept a string like `env:SOME_VARIABLE` and read the contents from the environment variable `SOME_VARIABLE`.
1 parent ac19856 commit 0a7d5d4

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

libcaf_openssl/src/openssl/session.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
#include "caf/openssl/session.hpp"
66

7+
#include <optional>
8+
#include <openssl/decoder.h>
9+
710
CAF_PUSH_WARNINGS
811
#include <openssl/err.h>
912
CAF_POP_WARNINGS
@@ -56,6 +59,33 @@ int pem_passwd_cb(char* buf, int size, int, void* ptr) {
5659
return static_cast<int>(strlen(buf));
5760
}
5861

62+
// If the input is a string like `env:SOME_VARIABLE` and the environment variable
63+
// `SOME_VARIABLE` exists, returns a string with the value of `SOME_VARIABLE`.
64+
// Otherwise, returns `std::nullopt`.
65+
std::optional<std::string> contents_from_indirect_envvar(const std::string& str) {
66+
if (str.find("env:") != 0)
67+
return std::nullopt;
68+
auto var = str.substr(4);
69+
auto const* contents = ::getenv(var.c_str());
70+
if (contents == nullptr)
71+
return std::nullopt;
72+
return std::string{contents};
73+
}
74+
75+
// If the input is a string like `env:SOME_VARIABLE` and the environment variable
76+
// `SOME_VARIABLE` exists, returns a string with the value of `SOME_VARIABLE`.
77+
std::optional<std::string> tmpfile_from_indirect_envvar(const std::string& str) {
78+
auto contents = contents_from_indirect_envvar(str);
79+
if (!contents)
80+
return contents;
81+
auto filename = std::string{"/tmp/caf-openssl.XXXXXX"};
82+
::mkstemp(&filename[0]);
83+
std::ofstream ofs(filename);
84+
ofs << *contents;
85+
ofs.close();
86+
return filename;
87+
}
88+
5989
} // namespace
6090

6191
session::session(actor_system& sys)
@@ -207,16 +237,47 @@ SSL_CTX* session::create_ssl_context() {
207237
if (sys_.openssl_manager().authentication_enabled()) {
208238
// Require valid certificates on both sides.
209239
auto& cfg = sys_.config();
210-
if (!cfg.openssl_certificate.empty()
240+
// OpenSSL doesn't expose an API to read a certificate chain PEM from
241+
// memory (as far as I can tell, there might be something in OSSL_DECODER)
242+
// and the implementation of `SSL_CTX_use_certificate_chain_file`
243+
// is huge, so we just write into a temporary file.
244+
if (auto filename = tmpfile_from_indirect_envvar(cfg.openssl_certificate)) {
245+
if (SSL_CTX_use_certificate_chain_file(ctx, filename->c_str())
246+
!= 1)
247+
CAF_RAISE_ERROR("cannot load certificate from environt variable");
248+
} else if (!cfg.openssl_certificate.empty()
211249
&& SSL_CTX_use_certificate_chain_file(ctx,
212250
cfg.openssl_certificate.c_str())
213-
!= 1)
251+
!= 1) {
214252
CAF_RAISE_ERROR("cannot load certificate");
253+
}
215254
if (!cfg.openssl_passphrase.empty()) {
216255
openssl_passphrase_ = cfg.openssl_passphrase;
217256
SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
218257
SSL_CTX_set_default_passwd_cb_userdata(ctx, this);
219258
}
259+
if (auto var = contents_from_indirect_envvar(cfg.openssl_key)) {
260+
EVP_PKEY *pkey = nullptr;
261+
const char *format = "PEM"; // NULL for any format
262+
const char *structure = nullptr; // Any structure
263+
const char *keytype = nullptr; // Any key
264+
auto const* datap = reinterpret_cast<const unsigned char*>(var->data());
265+
auto len = var->size();
266+
int selection = 0; // Autodetect selection.
267+
// TODO: We might as well read `openssl_passphrase` here and pass it
268+
// to the decoder.
269+
auto* dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure,
270+
keytype,
271+
selection,
272+
nullptr, nullptr);
273+
if (dctx == nullptr)
274+
CAF_RAISE_ERROR("couldn't create openssl decoder context");
275+
if (OSSL_DECODER_from_data(dctx, &datap, &len) != 0)
276+
CAF_RAISE_ERROR("failed to decode private key");
277+
// Use the private key.
278+
SSL_CTX_use_PrivateKey(ctx, pkey);
279+
OSSL_DECODER_CTX_free(dctx);
280+
}
220281
if (!cfg.openssl_key.empty()
221282
&& SSL_CTX_use_PrivateKey_file(ctx, cfg.openssl_key.c_str(),
222283
SSL_FILETYPE_PEM)
@@ -226,6 +287,9 @@ SSL_CTX* session::create_ssl_context() {
226287
: nullptr);
227288
auto capath = (!cfg.openssl_capath.empty() ? cfg.openssl_capath.c_str()
228289
: nullptr);
290+
auto tmpfile = tmpfile_from_indirect_envvar(cafile);
291+
if (tmpfile)
292+
cafile = tmpfile->c_str();
229293
if (cafile || capath) {
230294
if (SSL_CTX_load_verify_locations(ctx, cafile, capath) != 1)
231295
CAF_RAISE_ERROR("cannot load trusted CA certificates");

0 commit comments

Comments
 (0)