Skip to content

Commit

Permalink
cloud-auth: implement gcp(user-managed-service-account())
Browse files Browse the repository at this point in the history
Signed-off-by: Attila Szakacs <attila.szakacs@axoflow.com>
  • Loading branch information
alltilla committed Dec 14, 2023
1 parent eadb03f commit 8ca8d16
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 8 deletions.
15 changes: 15 additions & 0 deletions modules/cloud-auth/CMakeLists.txt
Expand Up @@ -8,6 +8,19 @@ if(NOT ENABLE_CLOUD_AUTH OR NOT ENABLE_CPP)
return()
endif()

if (NOT DEFINED ENABLE_CLOUD_AUTH_CURL OR ENABLE_CLOUD_AUTH_CURL)
find_package(Curl)
endif()

module_switch(ENABLE_CLOUD_AUTH_CURL "Enable cURL support for cloud-auth()" Curl_FOUND)
if (NOT ENABLE_CLOUD_AUTH_CURL)
return ()
endif ()

if (NOT Curl_FOUND)
message(FATAL_ERROR "cURL support for cloud-auth() enabled, but libcurl not found")
endif ()

set(CLOUD_AUTH_CPP_SOURCES
cloud-auth.c
cloud-auth.h
Expand All @@ -30,6 +43,8 @@ add_module(
INCLUDES ${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/modules/cloud-auth
${PROJECT_SOURCE_DIR}/modules/cloud-auth/jwt-cpp/include
${Curl_INCLUDE_DIR}
DEPENDS ${Curl_LIBRARIES}
LIBRARY_TYPE STATIC
)

Expand Down
3 changes: 2 additions & 1 deletion modules/cloud-auth/Makefile.am
Expand Up @@ -10,12 +10,13 @@ modules_cloud_auth_libcloud_auth_cpp_la_SOURCES = \

modules_cloud_auth_libcloud_auth_cpp_la_CXXFLAGS = \
$(AM_CXXFLAGS) \
$(LIBCURL_CFLAGS) \
-I$(top_srcdir)/modules/cloud-auth \
-I$(top_builddir)/modules/cloud-auth \
-isystem $(top_srcdir)/modules/cloud-auth/jwt-cpp/include \
-isystem $(top_builddir)/modules/cloud-auth/jwt-cpp/include

modules_cloud_auth_libcloud_auth_cpp_la_LIBADD = $(MODULE_DEPS_LIBS)
modules_cloud_auth_libcloud_auth_cpp_la_LIBADD = $(MODULE_DEPS_LIBS) $(LIBCURL_LIBS)
modules_cloud_auth_libcloud_auth_cpp_la_LDFLAGS = $(MODULE_LDFLAGS)
modules_cloud_auth_libcloud_auth_cpp_la_DEPENDENCIES = $(MODULE_DEPS_LIBS)

Expand Down
162 changes: 160 additions & 2 deletions modules/cloud-auth/google-auth.cpp
Expand Up @@ -103,14 +103,172 @@ ServiceAccountAuthenticator::handle_http_header_request(HttpHeaderRequestSignalD

UserManagedServiceAccountAuthenticator::UserManagedServiceAccountAuthenticator(const char *name_,
const char *metadata_url_)
: name(name_), metadata_url(metadata_url_)
: name(name_)
{
auth_url = metadata_url_;
auth_url.append("/");
auth_url.append(name);
auth_url.append("/token");

curl_headers = curl_slist_append(NULL, "Metadata-Flavor: Google");
}

UserManagedServiceAccountAuthenticator::~UserManagedServiceAccountAuthenticator()
{
curl_slist_free_all(curl_headers);
}

void
UserManagedServiceAccountAuthenticator::add_token_to_headers(HttpHeaderRequestSignalData *data,
const std::string &token)
{
/* Scratch Buffers are marked at this point in http-worker.c */
GString *header_buffer = scratch_buffers_alloc();
g_string_append(header_buffer, "Authorization: Bearer ");
g_string_append(header_buffer, token.c_str());

list_append(data->request_headers, header_buffer->str);
}

size_t
UserManagedServiceAccountAuthenticator::curl_write_callback(void *contents, size_t size, size_t nmemb, void *userp)
{
const char *data = (const char *) contents;
std::string *response_payload_buffer = (std::string *) userp;

size_t real_size = size * nmemb;
response_payload_buffer->append(data, real_size);

return real_size;
}

bool
UserManagedServiceAccountAuthenticator::send_token_get_request(std::string &response_payload_buffer)
{
CURLcode res;
CURL *curl = curl_easy_init();
if (!curl)
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"failed to init cURL handle",
evt_tag_str("url", auth_url.c_str()));
goto error;
}

curl_easy_setopt(curl, CURLOPT_URL, auth_url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &response_payload_buffer);

res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"error sending HTTP request to metadata server",
evt_tag_str("url", auth_url.c_str()),
evt_tag_str("error", curl_easy_strerror(res)));
goto error;
}

long http_result_code;
res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_result_code);
if (res != CURLE_OK)
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"failed to get HTTP result code",
evt_tag_str("url", auth_url.c_str()),
evt_tag_str("error", curl_easy_strerror(res)));
goto error;
}

if (http_result_code != 200)
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"non 200 HTTP result code received",
evt_tag_str("url", auth_url.c_str()),
evt_tag_int("http_result_code", http_result_code));
goto error;
}

curl_easy_cleanup(curl);
return true;

error:
if (curl)
curl_easy_cleanup(curl);
return false;
}

bool
UserManagedServiceAccountAuthenticator::parse_token_and_expiry_from_response(const std::string &response_payload,
std::string &token, long *expiry)
{
picojson::value json;
std::string json_parse_error = picojson::parse(json, response_payload);
if (!json_parse_error.empty())
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"failed to parse response JSON",
evt_tag_str("url", auth_url.c_str()),
evt_tag_str("response_json", response_payload.c_str()));
return false;
}

if (!json.is<picojson::object>() || !json.contains("access_token") || !json.contains("expires_in"))
{
msg_error("cloud_auth::google::UserManagedServiceAccountAuthenticator: "
"unexpected response JSON",
evt_tag_str("url", auth_url.c_str()),
evt_tag_str("response_json", response_payload.c_str()));
return false;
}

token.assign(json.get("access_token").get<std::string>());
*expiry = json.get("expires_in").get<long>();
return true;
}

void
UserManagedServiceAccountAuthenticator::handle_http_header_request(HttpHeaderRequestSignalData *data)
{
/* TODO: implement based on https://cloud.google.com/compute/docs/access/authenticate-workloads#curl */
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();

lock.lock();

if (now <= refresh_token_after && !cached_token.empty())
{
add_token_to_headers(data, cached_token);
lock.unlock();

data->result = HTTP_SLOT_SUCCESS;
return;
}

cached_token.clear();

std::string response_payload_buffer;
if (!send_token_get_request(response_payload_buffer))
{
lock.unlock();

data->result = HTTP_SLOT_CRITICAL_ERROR;
return;
}

long expiry;
if (!parse_token_and_expiry_from_response(response_payload_buffer, cached_token, &expiry))
{
lock.unlock();

data->result = HTTP_SLOT_CRITICAL_ERROR;
return;
}

refresh_token_after = now + std::chrono::seconds{expiry - 60};
add_token_to_headers(data, cached_token);

lock.unlock();

data->result = HTTP_SLOT_SUCCESS;
}

Expand Down
2 changes: 0 additions & 2 deletions modules/cloud-auth/google-auth.h
Expand Up @@ -41,8 +41,6 @@ void google_authenticator_set_service_account_token_validity_duration(CloudAuthe
guint64 token_validity_duration);

void google_authenticator_set_user_managed_service_account_name(CloudAuthenticator *s, const gchar *name);
void google_authenticator_set_user_managed_service_account_token_validity_duration(CloudAuthenticator *s,
guint64 token_validity_duration);
void google_authenticator_set_user_managed_service_account_metadata_url(CloudAuthenticator *s,
const gchar *metadata_url);

Expand Down
20 changes: 17 additions & 3 deletions modules/cloud-auth/google-auth.hpp
Expand Up @@ -29,10 +29,13 @@

#include "cloud-auth.hpp"

#include <mutex>
#include <string>
#include <jwt-cpp/jwt.h>
#include <picojson/picojson.h>

#include "compat/curl.h"

namespace syslogng {
namespace cloud_auth {
namespace google {
Expand All @@ -58,14 +61,25 @@ class UserManagedServiceAccountAuthenticator: public syslogng::cloud_auth::Authe
{
public:
UserManagedServiceAccountAuthenticator(const char *name, const char *metadata_url);
~UserManagedServiceAccountAuthenticator() {};
~UserManagedServiceAccountAuthenticator();

void handle_http_header_request(HttpHeaderRequestSignalData *data);

private:
static void add_token_to_headers(HttpHeaderRequestSignalData *data, const std::string &token);
static size_t curl_write_callback(void *contents, size_t size, size_t nmemb, void *userp);
bool send_token_get_request(std::string &response_payload_buffer);
bool parse_token_and_expiry_from_response(const std::string &response_payload, std::string &token, long *expiry);

private:
std::string name;
uint64_t token_validity_duration;
std::string metadata_url;
std::string auth_url;

struct curl_slist *curl_headers;

std::mutex lock;
std::string cached_token;
std::chrono::system_clock::time_point refresh_token_after;
};
}
}
Expand Down

0 comments on commit 8ca8d16

Please sign in to comment.