Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cloud-auth: implement gcp(user-managed-service-account()) #4755

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 16 additions & 1 deletion 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,11 +43,13 @@ 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
)

add_module(
TARGET cloud-auth
TARGET cloud_auth
GRAMMAR cloud-auth-grammar
DEPENDS cloud-auth-cpp
INCLUDES ${PROJECT_SOURCE_DIR}
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
18 changes: 18 additions & 0 deletions modules/cloud-auth/cloud-auth-grammar.ym
Expand Up @@ -52,6 +52,9 @@ CloudAuthenticator *last_authenticator;
%token KW_KEY
%token KW_AUDIENCE
%token KW_TOKEN_VALIDITY_DURATION
%token KW_USER_MANAGED_SERVICE_ACCOUNT
%token KW_NAME
%token KW_METADATA_URL

%%

Expand Down Expand Up @@ -91,6 +94,11 @@ google_dest_auth_option
google_authenticator_set_auth_mode(last_authenticator, GAAM_SERVICE_ACCOUNT);
}
'(' google_dest_auth_service_account_options ')'
| KW_USER_MANAGED_SERVICE_ACCOUNT
{
google_authenticator_set_auth_mode(last_authenticator, GAAM_USER_MANAGED_SERVICE_ACCOUNT);
}
'(' google_dest_auth_user_managed_service_account_options ')'
;

google_dest_auth_service_account_options
Expand All @@ -104,6 +112,16 @@ google_dest_auth_service_account_option
| KW_TOKEN_VALIDITY_DURATION '(' nonnegative_integer64 ')' { google_authenticator_set_service_account_token_validity_duration(last_authenticator, $3); }
;

google_dest_auth_user_managed_service_account_options
: google_dest_auth_user_managed_service_account_option google_dest_auth_user_managed_service_account_options
|
;

google_dest_auth_user_managed_service_account_option
: KW_NAME '(' string ')' { google_authenticator_set_user_managed_service_account_name(last_authenticator, $3); free($3); }
| KW_METADATA_URL '(' string ')' { google_authenticator_set_user_managed_service_account_metadata_url(last_authenticator, $3); free($3); }
;

/* INCLUDE_RULES */

%%
3 changes: 3 additions & 0 deletions modules/cloud-auth/cloud-auth-parser.c
Expand Up @@ -35,6 +35,9 @@ static CfgLexerKeyword cloud_auth_keywords[] =
{ "key", KW_KEY },
{ "audience", KW_AUDIENCE },
{ "token_validity_duration", KW_TOKEN_VALIDITY_DURATION },
{ "user_managed_service_account", KW_USER_MANAGED_SERVICE_ACCOUNT },
{ "name", KW_NAME},
{ "metadata_url", KW_METADATA_URL },
{ NULL }
};

Expand Down
217 changes: 217 additions & 0 deletions modules/cloud-auth/google-auth.cpp
Expand Up @@ -24,6 +24,7 @@

#include <exception>
#include <fstream>
#include <cmath>

#include "compat/cpp-start.h"
#include "scratch-buffers.h"
Expand Down Expand Up @@ -101,6 +102,177 @@ ServiceAccountAuthenticator::handle_http_header_request(HttpHeaderRequestSignalD
data->result = HTTP_SLOT_SUCCESS;
}

UserManagedServiceAccountAuthenticator::UserManagedServiceAccountAuthenticator(const char *name_,
const char *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 = lround(json.get("expires_in").get<double>()); /* getting a long from picojson is not always available */
return true;
}

void
UserManagedServiceAccountAuthenticator::handle_http_header_request(HttpHeaderRequestSignalData *data)
{
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;
}

/* C Wrappers */

typedef struct _GoogleAuthenticator
Expand All @@ -114,6 +286,13 @@ typedef struct _GoogleAuthenticator
gchar *audience;
guint64 token_validity_duration;
} service_account_options;

struct
{
gchar *name;
gchar *metadata_url;
} user_managed_service_account_options;

} GoogleAuthenticator;

void
Expand Down Expand Up @@ -150,6 +329,24 @@ google_authenticator_set_service_account_token_validity_duration(CloudAuthentica
self->service_account_options.token_validity_duration = token_validity_duration;
}

void
google_authenticator_set_user_managed_service_account_name(CloudAuthenticator *s, const gchar *name)
{
GoogleAuthenticator *self = (GoogleAuthenticator *) s;

g_free(self->user_managed_service_account_options.name);
self->user_managed_service_account_options.name = g_strdup(name);
}

void
google_authenticator_set_user_managed_service_account_metadata_url(CloudAuthenticator *s, const gchar *metadata_url)
{
GoogleAuthenticator *self = (GoogleAuthenticator *) s;

g_free(self->user_managed_service_account_options.metadata_url);
self->user_managed_service_account_options.metadata_url = g_strdup(metadata_url);
}

static gboolean
_init(CloudAuthenticator *s)
{
Expand All @@ -171,6 +368,19 @@ _init(CloudAuthenticator *s)
return FALSE;
}
break;
case GAAM_USER_MANAGED_SERVICE_ACCOUNT:
try
{
self->super.cpp = new UserManagedServiceAccountAuthenticator(self->user_managed_service_account_options.name,
self->user_managed_service_account_options.metadata_url);
}
catch (const std::runtime_error &e)
{
msg_error("cloud_auth::google: Failed to initialize UserManagedServiceAccountAuthenticator",
evt_tag_str("error", e.what()));
return FALSE;
}
break;
case GAAM_UNDEFINED:
msg_error("cloud_auth::google: Failed to initialize ServiceAccountAuthenticator",
evt_tag_str("error", "Authentication mode must be set (e.g. service-account())"));
Expand All @@ -186,6 +396,10 @@ static void
_set_default_options(GoogleAuthenticator *self)
{
self->service_account_options.token_validity_duration = 3600;

self->user_managed_service_account_options.name = g_strdup("default");
self->user_managed_service_account_options.metadata_url =
g_strdup("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts");
}

static void
Expand All @@ -195,6 +409,9 @@ _free(CloudAuthenticator *s)

g_free(self->service_account_options.key_path);
g_free(self->service_account_options.audience);

g_free(self->user_managed_service_account_options.name);
g_free(self->user_managed_service_account_options.metadata_url);
}

CloudAuthenticator *
Expand Down
6 changes: 6 additions & 0 deletions modules/cloud-auth/google-auth.h
Expand Up @@ -29,14 +29,20 @@ typedef enum _GoogleAuthenticatorAuthMode
{
GAAM_UNDEFINED,
GAAM_SERVICE_ACCOUNT,
GAAM_USER_MANAGED_SERVICE_ACCOUNT,
} GoogleAuthenticatorAuthMode;

CloudAuthenticator *google_authenticator_new(void);
void google_authenticator_set_auth_mode(CloudAuthenticator *s, GoogleAuthenticatorAuthMode auth_mode);

void google_authenticator_set_service_account_key_path(CloudAuthenticator *s, const gchar *key_path);
void google_authenticator_set_service_account_audience(CloudAuthenticator *s, const gchar *audience);
void google_authenticator_set_service_account_token_validity_duration(CloudAuthenticator *s,
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_metadata_url(CloudAuthenticator *s,
const gchar *metadata_url);


#endif