From 43aaf95cd16aba07f383be5a65e4942910651382 Mon Sep 17 00:00:00 2001 From: Hui Yu Date: Tue, 18 Aug 2020 00:15:20 +0800 Subject: [PATCH] [Configuration] 1. Authentication provider plugin framework 2. An instance of authentication provider plugin for OIDC (OpenID Connect) --- .github/workflows/build.yml | 7 + examples/Makefile | 2 + examples/auth_provider/.gitignore | 3 + examples/auth_provider/Makefile | 8 + .../config_with_auth_provider_sample | 32 + examples/auth_provider/main.c | 64 ++ kubernetes/CMakeLists.txt | 6 + kubernetes/config/authn_plugin/authn_plugin.c | 81 +++ kubernetes/config/authn_plugin/authn_plugin.h | 25 + .../config/authn_plugin/authn_plugin_util.c | 76 +++ .../config/authn_plugin/authn_plugin_util.h | 75 +++ .../authn_plugin/plugins/oidc/CMakeLists.txt | 15 + .../plugins/oidc/libkubernetes_oidc.c | 318 ++++++++++ kubernetes/config/kube_config.c | 147 ++--- kubernetes/config/kube_config_common.h | 2 + kubernetes/config/kube_config_model.c | 28 + kubernetes/config/kube_config_model.h | 10 +- kubernetes/config/kube_config_util.c | 123 ++++ kubernetes/config/kube_config_util.h | 100 +++ kubernetes/config/kube_config_yaml.c | 567 +++++++++++++++++- kubernetes/config/kube_config_yaml.h | 20 + 21 files changed, 1612 insertions(+), 97 deletions(-) create mode 100644 examples/auth_provider/.gitignore create mode 100644 examples/auth_provider/Makefile create mode 100644 examples/auth_provider/config_with_auth_provider_sample create mode 100644 examples/auth_provider/main.c create mode 100644 kubernetes/config/authn_plugin/authn_plugin.c create mode 100644 kubernetes/config/authn_plugin/authn_plugin.h create mode 100644 kubernetes/config/authn_plugin/authn_plugin_util.c create mode 100644 kubernetes/config/authn_plugin/authn_plugin_util.h create mode 100644 kubernetes/config/authn_plugin/plugins/oidc/CMakeLists.txt create mode 100644 kubernetes/config/authn_plugin/plugins/oidc/libkubernetes_oidc.c create mode 100644 kubernetes/config/kube_config_util.c create mode 100644 kubernetes/config/kube_config_util.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44bfe03..398d14e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,13 @@ jobs: cd build cmake .. make + - name: Build authentication plugin - oidc + run: | + cd kubernetes/config/authn_plugin/plugins/oidc + mkdir build + cd build + cmake .. + make - name: Build examples run: | cd examples/ diff --git a/examples/Makefile b/examples/Makefile index f50bfbe..9878207 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -4,6 +4,7 @@ all: cd list_pod_incluster; make cd exec_provider; make cd generic; make + cd auth_provider; make clean: cd create_pod; make clean @@ -11,3 +12,4 @@ clean: cd list_pod_incluster; make clean cd exec_provider; make clean cd generic; make clean + cd auth_provider; make clean diff --git a/examples/auth_provider/.gitignore b/examples/auth_provider/.gitignore new file mode 100644 index 0000000..b35e061 --- /dev/null +++ b/examples/auth_provider/.gitignore @@ -0,0 +1,3 @@ +list_pod_by_auth_provider_bin +config_with_auth_provider +config_with_auth_provider.* diff --git a/examples/auth_provider/Makefile b/examples/auth_provider/Makefile new file mode 100644 index 0000000..ef1f632 --- /dev/null +++ b/examples/auth_provider/Makefile @@ -0,0 +1,8 @@ +INCLUDE:=-I../../kubernetes/include -I../../kubernetes/model -I../../kubernetes/api -I../../kubernetes/config +LIBS:=-L../../kubernetes/build -lkubernetes -lcurl -lyaml -lpthread -lssl -lz +CFLAGS:=-g + +all: + gcc main.c $(CFLAGS) $(INCLUDE) $(LIBS) -o list_pod_by_auth_provider_bin +clean: + rm ./list_pod_by_auth_provider_bin diff --git a/examples/auth_provider/config_with_auth_provider_sample b/examples/auth_provider/config_with_auth_provider_sample new file mode 100644 index 0000000..fbae906 --- /dev/null +++ b/examples/auth_provider/config_with_auth_provider_sample @@ -0,0 +1,32 @@ +--- +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: + server: https://host:6443 + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: kubernetes-admin + name: kubernetes-admin@kubernetes +- context: + cluster: kubernetes + namespace: default + user: theone + name: theone@kubernetes +current-context: theone@kubernetes +kind: Config +preferences: {} +users: +- name: theone + user: + auth-provider: + name: oidc + config: + client-id: + client-secret: + id-token: + idp-certificate-authority: + idp-issuer-url: + refresh-token: diff --git a/examples/auth_provider/main.c b/examples/auth_provider/main.c new file mode 100644 index 0000000..88b0fe5 --- /dev/null +++ b/examples/auth_provider/main.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include + +void list_pod(apiClient_t * apiClient) +{ + v1_pod_list_t *pod_list = NULL; + pod_list = CoreV1API_listNamespacedPod(apiClient, "default", /*namespace */ + NULL, /* pretty */ + 0, /* allowWatchBookmarks */ + NULL, /* continue */ + NULL, /* fieldSelector */ + NULL, /* labelSelector */ + 0, /* limit */ + NULL, /* resourceVersion */ + 0, /* timeoutSeconds */ + 0 /* watch */ + ); + printf("The return code of HTTP request=%ld\n", apiClient->response_code); + if (pod_list) { + printf("Get pod list:\n"); + listEntry_t *listEntry = NULL; + v1_pod_t *pod = NULL; + list_ForEach(listEntry, pod_list->items) { + pod = listEntry->data; + printf("\tThe pod name: %s\n", pod->metadata->name); + } + v1_pod_list_free(pod_list); + pod_list = NULL; + } else { + printf("Cannot get any pod.\n"); + } +} + +int main(int argc, char *argv[]) +{ + char *basePath = NULL; + sslConfig_t *sslConfig = NULL; + list_t *apiKeys = NULL; + int rc = load_kube_config(&basePath, &sslConfig, &apiKeys, "./config_with_auth_provider"); + if (rc != 0) { + printf("Cannot load kubernetes configuration.\n"); + return -1; + } + apiClient_t *apiClient = apiClient_create_with_base_path(basePath, sslConfig, apiKeys); + if (!apiClient) { + printf("Cannot create a kubernetes client.\n"); + return -1; + } + + list_pod(apiClient); + + apiClient_free(apiClient); + apiClient = NULL; + free_client_config(basePath, sslConfig, apiKeys); + basePath = NULL; + sslConfig = NULL; + apiKeys = NULL; + + return 0; +} diff --git a/kubernetes/CMakeLists.txt b/kubernetes/CMakeLists.txt index d869610..f658ecf 100644 --- a/kubernetes/CMakeLists.txt +++ b/kubernetes/CMakeLists.txt @@ -34,9 +34,12 @@ endif() set(SRCS config/kube_config_model.c config/kube_config_yaml.c + config/kube_config_util.c config/kube_config.c config/incluster_config.c config/exec_provider.c + config/authn_plugin/authn_plugin_util.c + config/authn_plugin/authn_plugin.c src/list.c src/apiKey.c src/apiClient.c @@ -793,9 +796,12 @@ set(HDRS config/kube_config_common.h config/kube_config_model.h config/kube_config_yaml.h + config/kube_config_util.h config/kube_config.h config/incluster_config.h config/exec_provider.h + config/authn_plugin/authn_plugin_util.h + config/authn_plugin/authn_plugin.h include/apiClient.h include/list.h include/binary.h diff --git a/kubernetes/config/authn_plugin/authn_plugin.c b/kubernetes/config/authn_plugin/authn_plugin.c new file mode 100644 index 0000000..bc504f4 --- /dev/null +++ b/kubernetes/config/authn_plugin/authn_plugin.c @@ -0,0 +1,81 @@ +#include "authn_plugin.h" +#include +#include +#include +#include + +#define PLUGIN_NAME_TEMPLATE "libkubernetes_%s.so" +#define PLUGIN_LIB_NAME_SIZE 64 + +#define PLUGIN_FUNCTION_GET_TOKEN "get_token" +#define PLUGIN_FUNCTION_IS_EXPIRED "is_expired" +#define PLUGIN_FUNCTION_REFRESH "refresh" + +authn_plugin_t *create_authn_plugin(const char *name) +{ + static char fname[] = "create_authn_plugin()"; + + authn_plugin_t *plugin = calloc(1, sizeof(authn_plugin_t)); + if (!plugin) { + fprintf(stderr, "%s: Cannot allocate memory for plugin library %s.[%s]\n", fname, name, strerror(errno)); + return NULL; + } + + char plugin_lib_name[PLUGIN_LIB_NAME_SIZE]; + memset(plugin_lib_name, 0, sizeof(plugin_lib_name)); + snprintf(plugin_lib_name, sizeof(plugin_lib_name), PLUGIN_NAME_TEMPLATE, name); + void *dlhandler = dlopen(plugin_lib_name, RTLD_LAZY); + + if (!dlhandler) { + fprintf(stderr, "%s: Cannot load the library %s.[%s]\n", fname, plugin_lib_name, dlerror()); + goto error; + } + + plugin->name = strdup(name); + plugin->dlhandler = dlhandler; + plugin->get_token = dlsym(dlhandler, PLUGIN_FUNCTION_GET_TOKEN); + if (!plugin->get_token) { + fprintf(stderr, "%s: Cannot find the function %s in library %s.[%s]\n", fname, PLUGIN_FUNCTION_GET_TOKEN, plugin_lib_name, dlerror()); + goto error; + } + plugin->is_expired = dlsym(dlhandler, PLUGIN_FUNCTION_IS_EXPIRED); + if (!plugin->is_expired) { + fprintf(stderr, "%s: Cannot find the function %s in library %s.[%s]\n", fname, PLUGIN_FUNCTION_IS_EXPIRED, plugin_lib_name, dlerror()); + goto error; + } + plugin->refresh = dlsym(dlhandler, PLUGIN_FUNCTION_REFRESH); + if (!plugin->refresh) { + fprintf(stderr, "%s: Cannot find the function %s in library %s.[%s]\n", fname, PLUGIN_FUNCTION_REFRESH, plugin_lib_name, dlerror()); + goto error; + } + + return plugin; + + error: + free_authn_plugin(plugin); + plugin = NULL; + return plugin; +} + +void free_authn_plugin(authn_plugin_t * plugin) +{ + if (!plugin) { + return; + } + + if (plugin->dlhandler) { + dlclose(plugin->dlhandler); + plugin->dlhandler = NULL; + } + + if (plugin->name) { + free(plugin->name); + plugin->name = NULL; + } + + plugin->get_token = NULL; + plugin->is_expired = NULL; + plugin->refresh = NULL; + + free(plugin); +} diff --git a/kubernetes/config/authn_plugin/authn_plugin.h b/kubernetes/config/authn_plugin/authn_plugin.h new file mode 100644 index 0000000..67a2eb7 --- /dev/null +++ b/kubernetes/config/authn_plugin/authn_plugin.h @@ -0,0 +1,25 @@ +#ifndef _AUTHN_PLUGIN_H +#define _AUTHN_PLUGIN_H + +#include +#include "../kube_config_model.h" + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct authn_plugin_t { + char *name; + void *dlhandler; + char *(*get_token) (kubeconfig_property_t *); + bool (*is_expired) (kubeconfig_property_t *); + int (*refresh) (kubeconfig_property_t *); + } authn_plugin_t; + + authn_plugin_t *create_authn_plugin(const char *); + void free_authn_plugin(authn_plugin_t *); + +#ifdef __cplusplus +} +#endif +#endif /* _AUTHN_PLUGIN_H */ diff --git a/kubernetes/config/authn_plugin/authn_plugin_util.c b/kubernetes/config/authn_plugin/authn_plugin_util.c new file mode 100644 index 0000000..6d6888a --- /dev/null +++ b/kubernetes/config/authn_plugin/authn_plugin_util.c @@ -0,0 +1,76 @@ +#include "authn_plugin_util.h" +#include + +int shc_request(char **p_http_response, int *p_http_response_length, char *type, const char *url, sslConfig_t * sc, list_t * apiKeys, list_t * contentType, char *post_data) +{ + static char fname[] = "shc_request()"; + + apiClient_t *http_client = apiClient_create_with_base_path(url, sc, apiKeys); + if (!http_client) { + fprintf(stderr, "%s: Cannot create http client. [%s].\n", fname, strerror(errno)); + return -1; + } + apiClient_invoke(http_client, NULL, NULL, NULL, NULL, NULL, contentType, post_data, type); + + int rc = http_client->response_code; + switch (rc) { + case HTTP_RC_OK: + *p_http_response = strndup((char *)http_client->dataReceived, http_client->dataReceivedLen); + *p_http_response_length = http_client->dataReceivedLen; + break; + default: + printf("%s: response_code=%ld\n", fname, http_client->response_code); + if (http_client->dataReceived) { + printf("%s: %s\n", fname, (char *)http_client->dataReceived); + } + break; + } + + if (http_client->dataReceived) { + free(http_client->dataReceived); + http_client->dataReceived = NULL; + http_client->dataReceivedLen = 0; + } + + if (http_client) { + apiClient_free(http_client); + http_client = NULL; + } + + return rc; +} + +char *shc_get_string_from_json(const char *json_string, const char *key) +{ + static char fname[] = "shc_get_string_from_json()"; + + char *res = NULL; + + if (!json_string || !key) { + return NULL; + } + + cJSON *json = cJSON_Parse(json_string); + if (!json) { + fprintf(stderr, "%s: Cannot create JSON from string.[%s].\n", fname, cJSON_GetErrorPtr()); + return NULL; + } + cJSON *value = cJSON_GetObjectItem(json, key); + if (!value) { + fprintf(stderr, "%s: Cannot get the value for %s.\n", fname, key); + goto end; + } + if (value->type != cJSON_String && value->type != cJSON_Object) { + fprintf(stderr, "%s: The value for %s is invalid.\n", fname, key); + goto end; + } + res = strdup(value->valuestring); + + end: + if (json) { + cJSON_Delete(json); + json = NULL; + } + + return res; +} diff --git a/kubernetes/config/authn_plugin/authn_plugin_util.h b/kubernetes/config/authn_plugin/authn_plugin_util.h new file mode 100644 index 0000000..483b9e0 --- /dev/null +++ b/kubernetes/config/authn_plugin/authn_plugin_util.h @@ -0,0 +1,75 @@ +#ifndef _AUTHN_PLUGIN_UTIL_H +#define _AUTHN_PLUGIN_UTIL_H + +#include "../../include/apiClient.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_REQUEST_GET "GET" +#define HTTP_REQUEST_POST "POST" +#define HTTP_REQUEST_DELETE "DELETE" +#define HTTP_REQUEST_PATCH "PATCH" + +typedef enum shc_http_rc_t { + HTTP_RC_OK = 200, + HTTP_RC_UNAUTHORIZED = 400 +} shc_http_rc_t; + +/* + * shc_request + * + * Description: + * + * Issue a http(s) request + * + * Return: + * + * HTTP request return code + * + * Parameter: + * + * IN: + * request_type : HTTP_REQUEST_GET,HTTP_REQUEST_POST,HTTP_REQUEST_DELETE or HTTP_REQUEST_PATCH + * url : http request url + * sc : security context for SSL/TLS + * apiKeys : key-value list including basic or bearer token for HTTP authentication + * contentType : string list including HTTP content type + * post_data : post data string when request_type is "POST" + * + * OUT: + * p_http_response : pointer that pointing to HTTP response raw string, the memory is allocated in shc_request, but user should free it out of shc_request after it is useless. + * p_http_response_length : pointer that pointing to the length of p_http_response + * + */ +int shc_request(char **p_http_response, int *p_http_response_length, char *request_type, const char *url, sslConfig_t * sc, list_t * apiKeys, list_t * contentType, char *post_data); + +/* + * shc_get_string_from_json + * + * Description: + * + * Get a string value from json by a key + * + * Return: + * + * char * : the value string + * NULL : cannot find the value by key + * + * Parameter: + * + * IN: + * json_string : a json string that includes key-value string + * key : the key needs to find + * + * OUT: + * None + * + */ +char *shc_get_string_from_json(const char *json_string, const char *key); + +#ifdef __cplusplus +} +#endif +#endif /* _AUTHN_PLUGIN_UTIL_H */ diff --git a/kubernetes/config/authn_plugin/plugins/oidc/CMakeLists.txt b/kubernetes/config/authn_plugin/plugins/oidc/CMakeLists.txt new file mode 100644 index 0000000..578da99 --- /dev/null +++ b/kubernetes/config/authn_plugin/plugins/oidc/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 2.6) +project (KubernetesClientAuthnPluginOIDC) + +set(CMAKE_BUILD_TYPE Debug) + +set(BIN_NAME "kubernetes_oidc") + +include_directories(../../) +include_directories(../../../) +include_directories(../../../../include/) +include_directories(../../../../external/) + +aux_source_directory(. DIR_SRCS) + +add_library(${BIN_NAME} SHARED ${DIR_SRCS}) \ No newline at end of file diff --git a/kubernetes/config/authn_plugin/plugins/oidc/libkubernetes_oidc.c b/kubernetes/config/authn_plugin/plugins/oidc/libkubernetes_oidc.c new file mode 100644 index 0000000..af60628 --- /dev/null +++ b/kubernetes/config/authn_plugin/plugins/oidc/libkubernetes_oidc.c @@ -0,0 +1,318 @@ +#include "authn_plugin.h" +#include "authn_plugin_util.h" +#include "kube_config_util.h" +#include "kube_config_common.h" +#include "binary.h" +#include "cJSON.h" +#include +#include + +#define OIDC_ID_TOKEN_DELIM "." +#define OIDC_ID_TOKEN_EXP "exp" +#define OIDC_CONFIGURATION_URL_TEMPLATE "%s/.well-known/openid-configuration" +#define OIDC_TOKEN_ENDPOINT "token_endpoint" +#define OIDC_ID_TOKEN "id_token" +#define OIDC_REFRESH_TOKEN "refresh_token" + +#define JWT_PART2_BUFFER_SIZE 1024 +#define BASE64_PADDING_STRING_SIZE 4 +#define BASE64_PADDING_CHAR "=" + +#define REFRESH_TOKEN_CONTENT_TYPE "application/x-www-form-urlencoded" +#define REFRESH_TOKEN_CREDENTIAL_TEMPLATE "%s:%s" +#define REFRESH_TOKEN_POST_DATA_TEMPLATE "refresh_token=%s&grant_type=refresh_token" + +#define OIDC_CONFIGURATION_URL_BUFFER_SIZE 1024 +#define REFRESH_TOKEN_CREDENTIAL_BUFFER_SIZE 1024 +#define REFRESH_TOKEN_POST_DATA_BUFFER_SIZE 1024 + +static time_t get_token_expiration_time(const char *token_string) +{ + static char fname[] = "get_token_expiration_time()"; + + time_t expiration_time = 0; + + if (!token_string || '\0' == token_string[0]) { + return 0; + } + char *dup_token_string = strdup(token_string); + if (!dup_token_string) { + return 0; + } + + char *p = NULL; + p = strtok(dup_token_string, OIDC_ID_TOKEN_DELIM); /* jwt header */ + if (!p) { + fprintf(stderr, "%s: The token <%s> is not a valid JWT token.\n", fname, token_string); + goto end; + } + p = strtok(NULL, OIDC_ID_TOKEN_DELIM); /* jwt part2 */ + if (!p) { + fprintf(stderr, "%s: The token <%s> is not a valid JWT token.\n", fname, token_string); + goto end; + } + + int base64_padding_length = 4 - strlen(p) % 4; + char base64_padding_string[BASE64_PADDING_STRING_SIZE]; + memset(base64_padding_string, 0, sizeof(base64_padding_string)); + for (int i = 0; i < base64_padding_length; i++) { + strncat(base64_padding_string, BASE64_PADDING_CHAR, 1); + } + + char jwt_part2_string[JWT_PART2_BUFFER_SIZE]; + memset(jwt_part2_string, 0, sizeof(jwt_part2_string)); + snprintf(jwt_part2_string, sizeof(jwt_part2_string), "%s%s", p, base64_padding_string); + + int decoded_bytes = 0; + char *b64decode = base64decode(jwt_part2_string, strlen(jwt_part2_string), &decoded_bytes); + if (!b64decode || 0 == decoded_bytes) { + fprintf(stderr, "%s: Base64 decodes failed.\n", fname); + goto end; + } + + cJSON *payload_JSON = cJSON_Parse(b64decode); + if (!payload_JSON) { + fprintf(stderr, "%s: Cannot create JSON from string.[%s].\n", fname, cJSON_GetErrorPtr()); + goto end; + } + cJSON *json_value = cJSON_GetObjectItem(payload_JSON, OIDC_ID_TOKEN_EXP); + if (!json_value || json_value->type != cJSON_Number) { + fprintf(stderr, "%s: Cannot get expiration time in id token.\n", fname); + goto end; + } + expiration_time = json_value->valueint; + + end: + if (payload_JSON) { + cJSON_Delete(payload_JSON); + payload_JSON = NULL; + } + if (b64decode) { + free(b64decode); + b64decode = NULL; + } + if (dup_token_string) { + free(dup_token_string); + dup_token_string = NULL; + } + return expiration_time; +} + +char *get_token(kubeconfig_property_t * auth_provider) +{ + return auth_provider->id_token; +} + +bool is_expired(kubeconfig_property_t * auth_provider) +{ + static char fname[] = "is_expired()"; + + if (NULL == auth_provider->id_token) { + fprintf(stderr, "%s: The id token is NULL.\n", fname); + return true; + } else if ('\0' == auth_provider->id_token[0]) { + fprintf(stderr, "%s: The id token is empty.\n", fname); + return true; + } + + time_t exp_time = get_token_expiration_time(auth_provider->id_token); + if (exp_time < time(NULL)) { + return true; + } + + return false; +} + +char *get_token_endpoint(const char *idp_issuer_url, sslConfig_t * sc) +{ + static char fname[] = "get_token_endpoint()"; + + char *token_endpoint = NULL; + + if (!idp_issuer_url || '\0' == idp_issuer_url[0]) { + fprintf(stderr, "%s: The parameters idp_issuer_url is NULL or empty.\n", fname); + return NULL; + } + if (!sc) { + fprintf(stderr, "%s: SSL configuration is required.\n", fname); + return NULL; + } + + char oidc_configuration_url[OIDC_CONFIGURATION_URL_BUFFER_SIZE]; + memset(oidc_configuration_url, 0, sizeof(oidc_configuration_url)); + snprintf(oidc_configuration_url, sizeof(oidc_configuration_url), OIDC_CONFIGURATION_URL_TEMPLATE, idp_issuer_url); + + char *http_response = NULL; + int http_response_length = 0; + int rc = shc_request(&http_response, &http_response_length, HTTP_REQUEST_GET, oidc_configuration_url, sc, NULL, NULL, NULL); + if (HTTP_RC_OK != rc) { + fprintf(stderr, "%s: Failed to get token endpoint.\n", fname); + goto end; + } + + token_endpoint = shc_get_string_from_json(http_response, OIDC_TOKEN_ENDPOINT); + + end: + if (http_response) { + free(http_response); + http_response_length = 0; + } + + return token_endpoint; +} + +static int refresh_oidc_token(kubeconfig_property_t * auth_provider, const char *token_endpoint, sslConfig_t * sc) +{ + static char fname[] = "refresh_oidc_token()"; + + int rc = 0; + + if (!auth_provider || !token_endpoint || !sc) { + fprintf(stderr, "%s: The parameters are not valid.\n", fname); + return -1; + } + + list_t *content_type = list_create(); + if (!content_type) { + fprintf(stderr, "%s: Cannot create list for content type.[%s]\n", fname, strerror(errno)); + return -1; + } + list_addElement(content_type, strdup(REFRESH_TOKEN_CONTENT_TYPE)); + + char refresh_token_credential[REFRESH_TOKEN_CREDENTIAL_BUFFER_SIZE]; + memset(refresh_token_credential, 0, sizeof(refresh_token_credential)); + snprintf(refresh_token_credential, sizeof(refresh_token_credential), REFRESH_TOKEN_CREDENTIAL_TEMPLATE, auth_provider->client_id, auth_provider->client_secret); + char *base64_credential = base64encode(refresh_token_credential, strlen(refresh_token_credential)); + if (!base64_credential) { + fprintf(stderr, "%s: Cannot encode refresh token with base64.\n", fname); + rc = -1; + goto end; + } + char basic_token_buffer[BASIC_TOKEN_BUFFER_SIZE]; + memset(basic_token_buffer, 0, sizeof(basic_token_buffer)); + snprintf(basic_token_buffer, sizeof(basic_token_buffer), BASIC_TOKEN_TEMPLATE, base64_credential); + + list_t *api_keys = list_create(); + if (!api_keys) { + fprintf(stderr, "%s: Cannot create list for refresh token.[%s]\n", fname, strerror(errno)); + rc = -1; + goto end; + } + keyValuePair_t *keyPairToken = keyValuePair_create(strdup(AUTH_TOKEN_KEY), strdup(basic_token_buffer)); + list_addElement(api_keys, keyPairToken); + + char refresh_token_post_data[REFRESH_TOKEN_POST_DATA_BUFFER_SIZE]; + memset(refresh_token_post_data, 0, sizeof(refresh_token_post_data)); + snprintf(refresh_token_post_data, sizeof(refresh_token_post_data), REFRESH_TOKEN_POST_DATA_TEMPLATE, auth_provider->refresh_token); + + char *http_response = NULL; + int http_response_length = 0; + rc = shc_request(&http_response, &http_response_length, HTTP_REQUEST_POST, token_endpoint, sc, api_keys, content_type, refresh_token_post_data); + if (HTTP_RC_OK == rc) { + rc = 0; // update return code + } else { + fprintf(stderr, "%s: Failed to refresh OIDC token.\n", fname); + rc = -1; + goto end; + } + + char *new_id_token = shc_get_string_from_json(http_response, OIDC_ID_TOKEN); + if (new_id_token) { + if (auth_provider->id_token) { + free(auth_provider->id_token); + auth_provider->id_token = NULL; + } + auth_provider->id_token = new_id_token; + } else { + fprintf(stderr, "%s: Failed to get the new OIDC token from the response.\n", fname); + rc = -1; + goto end; + } + + char *new_refresh_token = shc_get_string_from_json(http_response, OIDC_REFRESH_TOKEN); + if (new_refresh_token) { + if (auth_provider->refresh_token) { + free(auth_provider->refresh_token); + auth_provider->refresh_token = NULL; + } + auth_provider->refresh_token = new_refresh_token; + } else { + rc = -1; + fprintf(stderr, "%s: Failed to get the new refresh token from the response.\n", fname); + goto end; + } + + end: + if (http_response) { + free(http_response); + http_response = NULL; + http_response_length = 0; + } + if (base64_credential) { + free(base64_credential); + base64_credential = NULL; + } + if (api_keys) { + clear_and_free_string_pair_list(api_keys); + api_keys = NULL; + } + if (content_type) { + clear_and_free_string_list(content_type); + content_type = NULL; + } + return rc; +} + +int refresh(kubeconfig_property_t * auth_provider) +{ + static char fname[] = "refresh()"; + + int rc = 0; + + sslConfig_t *sc = NULL; + if (auth_provider->idp_certificate_authority && '\0' != auth_provider->idp_certificate_authority[0]) { + sc = sslConfig_create(NULL, NULL, auth_provider->idp_certificate_authority, 0); + } else if (auth_provider->idp_certificate_authority_data && '\0' != auth_provider->idp_certificate_authority_data[0]) { + char *idp_certificate_file = kubeconfig_mk_cert_key_tempfile(auth_provider->idp_certificate_authority_data); + if (!idp_certificate_file) { + fprintf(stderr, "%s: Failed to create the temporary file for certificate.\n", fname); + return -1; + } + sc = sslConfig_create(NULL, NULL, idp_certificate_file, 0); + free(idp_certificate_file); + idp_certificate_file = NULL; + } else { + sc = sslConfig_create(NULL, NULL, NULL, 1); + } + if (!sc) { + fprintf(stderr, "%s: Cannot create the SSL configuration.\n", fname); + return -1; + } + + char *token_endpoint = get_token_endpoint(auth_provider->idp_issuer_url, sc); + if (!token_endpoint) { + fprintf(stderr, "%s: Cannot get the token endpoint.\n", fname); + rc = -1; + goto end; + } + rc = refresh_oidc_token(auth_provider, token_endpoint, sc); + if (-1 == rc) { + fprintf(stderr, "%s: Failed to refresh OIDC token.\n", fname); + goto end; + } + + end: + if (token_endpoint) { + free(token_endpoint); + token_endpoint = NULL; + } + if (sc) { + if (NULL == auth_provider->idp_certificate_authority && auth_provider->idp_certificate_authority_data && '\0' != auth_provider->idp_certificate_authority_data[0]) { + unsetSslConfig(sc); + } + sslConfig_free(sc); + sc = NULL; + } + + return rc; +} diff --git a/kubernetes/config/kube_config.c b/kubernetes/config/kube_config.c index 5566a14..6597bd3 100644 --- a/kubernetes/config/kube_config.c +++ b/kubernetes/config/kube_config.c @@ -8,12 +8,13 @@ #include "kube_config.h" #include "kube_config_yaml.h" #include "kube_config_common.h" +#include "kube_config_util.h" #include "exec_provider.h" +#include "authn_plugin/authn_plugin.h" #define ENV_KUBECONFIG "KUBECONFIG" #define ENV_HOME "HOME" #define KUBE_CONFIG_DEFAULT_LOCATION "%s/.kube/config" -#define KUBE_CONFIG_TEMPFILE_NAME_TEMPLATE "/tmp/kubeconfig-XXXXXX" static int setBasePath(char **pBasePath, char *basePath) { @@ -25,82 +26,6 @@ static int setBasePath(char **pBasePath, char *basePath) return -1; } -static bool is_cert_or_key_base64_encoded(const char *data) -{ - if (NULL == strstr(data, "BEGIN")) { - return true; - } else { - return false; // e.g. "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" - } -} - -static char *kubeconfig_mk_cert_key_tempfile(const char *data) -{ - static char fname[] = "kubeconfig_mk_tempfile()"; - - const char *cert_key_data = NULL; - int cert_key_data_bytes = 0; - - bool is_data_base64_encoded = is_cert_or_key_base64_encoded(data); - if (true == is_data_base64_encoded) { - int decoded_bytes = 0; - char *b64decode = base64decode(data, strlen(data), &decoded_bytes); - if (!b64decode || 0 == decoded_bytes) { - fprintf(stderr, "%s: Base64 decodes failed.\n", fname); - return NULL; - } - cert_key_data = b64decode; - cert_key_data_bytes = decoded_bytes; - } else { // plain text, no need base64 decode - cert_key_data = data; - cert_key_data_bytes = strlen(cert_key_data); - } - - char tempfile_name_template[] = KUBE_CONFIG_TEMPFILE_NAME_TEMPLATE; - int fd = mkstemp(tempfile_name_template); - if (-1 == fd) { - fprintf(stderr, "%s: Creating temp file for kubeconfig failed with error [%s]\n", fname, strerror(errno)); - return NULL; - } - - int rc = write(fd, cert_key_data, cert_key_data_bytes); - close(fd); - if (true == is_data_base64_encoded && cert_key_data) { - free((char *) cert_key_data); // cast "const char *" to "char *" - cert_key_data = NULL; - } - if (-1 == rc) { - fprintf(stderr, "%s: Writing temp file failed with error [%s]\n", fname, strerror(errno)); - return NULL; - } - - return strdup(tempfile_name_template); -} - -static void kubeconfig_rm_tempfile(const char *filename) -{ - if (filename) { - unlink(filename); - } -} - -static void unsetSslConfig(sslConfig_t * sslConfig) -{ - if (!sslConfig) { - return; - } - - if (sslConfig->clientCertFile) { - kubeconfig_rm_tempfile(sslConfig->clientCertFile); - } - if (sslConfig->clientKeyFile) { - kubeconfig_rm_tempfile(sslConfig->clientKeyFile); - } - if (sslConfig->CACertFile) { - kubeconfig_rm_tempfile(sslConfig->CACertFile); - } -} - static int setSslConfig(sslConfig_t ** pSslConfig, const kubeconfig_property_t * cluster, const kubeconfig_property_t * user) { int rc = 0; @@ -290,6 +215,53 @@ static int kubeconfig_update_exec_command_path(kubeconfig_property_t * exec, con return rc; } +static int kuberconfig_auth_provider(kubeconfig_property_t * current_user, kubeconfig_t * kubeconfig) +{ + static char fname[] = "kuberconfig_auth_provider()"; + + if (!current_user || !current_user->auth_provider || !kubeconfig) { + return 0; + } + + kubeconfig_property_t *auth_provider = current_user->auth_provider; + if (!auth_provider->name) { + fprintf(stderr, "%s: The name of auth provider is not specified.\n", fname); + return -1; + } + + authn_plugin_t *plugin = create_authn_plugin(auth_provider->name); + if (!plugin) { + fprintf(stderr, "%s: Cannot instantiate the auth provider plugin for %s.\n", fname, auth_provider->name); + return -1; + } + + int rc = 0; + if (plugin->is_expired(auth_provider)) { + rc = plugin->refresh(auth_provider); + if (0 != rc) { + fprintf(stderr, "%s: Cannot refresh token of auth provider <%s>.\n", fname, auth_provider->name); + goto end; + } + rc = kubeyaml_save_kubeconfig(kubeconfig); + if (0 != rc) { + fprintf(stderr, "%s: Cannot persist to kubeconfig file: %s.\n", fname, kubeconfig->fileName); + goto end; + } + } + const char *token = plugin->get_token(auth_provider); + if (!token) { + fprintf(stderr, "%s: Cannot get token from auth provider <%s>.\n", fname, auth_provider->name); + rc = -1; + goto end; + } + current_user->token = strdup(token); + + end: + free_authn_plugin(plugin); + plugin = NULL; + return rc; +} + int load_kube_config(char **pBasePath, sslConfig_t ** pSslConfig, list_t ** pApiKeys, const char *configFileName) { static char fname[] = "load_kube_config()"; @@ -346,6 +318,14 @@ int load_kube_config(char **pBasePath, sslConfig_t ** pSslConfig, list_t ** pApi } } + if (current_user && current_user->auth_provider) { + rc = kuberconfig_auth_provider(current_user, kubeconfig); + if (0 != rc) { + fprintf(stderr, "%s: Cannot get token from authentication provider.\n", fname); + goto end; + } + } + if (current_cluster && current_cluster->server) { rc = setBasePath(pBasePath, current_cluster->server); if (0 != rc) { @@ -388,17 +368,6 @@ void free_client_config(char *basePath, sslConfig_t * sslConfig, list_t * apiKey } if (apiKeys) { - listEntry_t *listEntry = NULL; - list_ForEach(listEntry, apiKeys) { - keyValuePair_t *pair = listEntry->data; - if (pair->key) { - free(pair->key); - } - if (pair->value) { - free(pair->value); - } - keyValuePair_free(pair); - } - list_free(apiKeys); + clear_and_free_string_pair_list(apiKeys); } } diff --git a/kubernetes/config/kube_config_common.h b/kubernetes/config/kube_config_common.h index d3b66b7..2432bd5 100644 --- a/kubernetes/config/kube_config_common.h +++ b/kubernetes/config/kube_config_common.h @@ -8,6 +8,8 @@ extern "C" { #define AUTH_TOKEN_KEY "Authorization" #define BEARER_TOKEN_TEMPLATE "Bearer %s" #define BEARER_TOKEN_BUFFER_SIZE 1024 +#define BASIC_TOKEN_TEMPLATE "Basic %s" +#define BASIC_TOKEN_BUFFER_SIZE 1024 #ifdef __cplusplus } diff --git a/kubernetes/config/kube_config_model.c b/kubernetes/config/kube_config_model.c index 10e392c..bdb6e54 100644 --- a/kubernetes/config/kube_config_model.c +++ b/kubernetes/config/kube_config_model.c @@ -83,6 +83,10 @@ void kubeconfig_property_free(kubeconfig_property_t * property) free(property->password); property->password = NULL; } + if (property->token) { + free(property->token); + property->token = NULL; + } if (property->auth_provider) { kubeconfig_property_free(property->auth_provider); property->auth_provider = NULL; @@ -98,6 +102,10 @@ void kubeconfig_property_free(kubeconfig_property_t * property) free(property->cluster); property->cluster = NULL; } + if (property->namespace) { + free(property->namespace); + property->namespace = NULL; + } if (property->user) { free(property->user); property->user = NULL; @@ -146,10 +154,30 @@ void kubeconfig_property_free(kubeconfig_property_t * property) free(property->expiry); property->expiry = NULL; } + if (property->idp_certificate_authority) { + free(property->idp_certificate_authority); + property->idp_certificate_authority = NULL; + } if (property->idp_certificate_authority_data) { free(property->idp_certificate_authority_data); property->idp_certificate_authority_data = NULL; } + if (property->client_id) { + free(property->client_id); + property->client_id = NULL; + } + if (property->client_secret) { + free(property->client_secret); + property->client_secret = NULL; + } + if (property->idp_issuer_url) { + free(property->idp_issuer_url); + property->idp_issuer_url = NULL; + } + if (property->refresh_token) { + free(property->refresh_token); + property->refresh_token = NULL; + } } free(property); diff --git a/kubernetes/config/kube_config_model.h b/kubernetes/config/kube_config_model.h index 6df703f..1893cf9 100644 --- a/kubernetes/config/kube_config_model.h +++ b/kubernetes/config/kube_config_model.h @@ -45,6 +45,7 @@ extern "C" { union { struct { /* context */ char *cluster; + char *namespace; char *user; }; struct { /* cluster */ @@ -70,12 +71,17 @@ extern "C" { int args_count; }; struct { /* user auth provider */ - char *id_token; - char *cmd_path; char *access_token; + char *client_id; + char *client_secret; + char *cmd_path; char *expires_on; char *expiry; + char *id_token; + char *idp_certificate_authority; char *idp_certificate_authority_data; + char *idp_issuer_url; + char *refresh_token; }; }; } kubeconfig_property_t; diff --git a/kubernetes/config/kube_config_util.c b/kubernetes/config/kube_config_util.c new file mode 100644 index 0000000..edca18e --- /dev/null +++ b/kubernetes/config/kube_config_util.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include "../include/apiClient.h" + +#define KUBE_CONFIG_TEMPFILE_NAME_TEMPLATE "/tmp/kubeconfig-XXXXXX" + +static bool is_cert_or_key_base64_encoded(const char *data) +{ + if (NULL == strstr(data, "BEGIN")) { + return true; + } else { + return false; // e.g. "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" + } +} + +char *kubeconfig_mk_cert_key_tempfile(const char *data) +{ + static char fname[] = "kubeconfig_mk_tempfile()"; + + const char *cert_key_data = NULL; + int cert_key_data_bytes = 0; + + bool is_data_base64_encoded = is_cert_or_key_base64_encoded(data); + if (true == is_data_base64_encoded) { + int decoded_bytes = 0; + char *b64decode = base64decode(data, strlen(data), &decoded_bytes); + if (!b64decode || 0 == decoded_bytes) { + fprintf(stderr, "%s: Base64 decodes failed.\n", fname); + return NULL; + } + cert_key_data = b64decode; + cert_key_data_bytes = decoded_bytes; + } else { // plain text, no need base64 decode + cert_key_data = data; + cert_key_data_bytes = strlen(cert_key_data); + } + + char tempfile_name_template[] = KUBE_CONFIG_TEMPFILE_NAME_TEMPLATE; + int fd = mkstemp(tempfile_name_template); + if (-1 == fd) { + fprintf(stderr, "%s: Creating temp file for kubeconfig failed with error [%s]\n", fname, strerror(errno)); + return NULL; + } + + int rc = write(fd, cert_key_data, cert_key_data_bytes); + close(fd); + if (true == is_data_base64_encoded && cert_key_data) { + free((char *) cert_key_data); // cast "const char *" to "char *" + cert_key_data = NULL; + } + if (-1 == rc) { + fprintf(stderr, "%s: Writing temp file failed with error [%s]\n", fname, strerror(errno)); + return NULL; + } + + return strdup(tempfile_name_template); +} + +static void kubeconfig_rm_tempfile(const char *filename) +{ + if (filename) { + unlink(filename); + } +} + +void unsetSslConfig(sslConfig_t * sslConfig) +{ + if (!sslConfig) { + return; + } + + if (sslConfig->clientCertFile) { + kubeconfig_rm_tempfile(sslConfig->clientCertFile); + } + if (sslConfig->clientKeyFile) { + kubeconfig_rm_tempfile(sslConfig->clientKeyFile); + } + if (sslConfig->CACertFile) { + kubeconfig_rm_tempfile(sslConfig->CACertFile); + } +} + +void clear_and_free_string_pair_list(list_t * list) +{ + if (!list) { + return; + } + + listEntry_t *listEntry = NULL; + list_ForEach(listEntry, list) { + keyValuePair_t *pair = listEntry->data; + if (pair->key) { + free(pair->key); + pair->key = NULL; + } + if (pair->value) { + free(pair->value); + pair->value = NULL; + } + keyValuePair_free(pair); + pair = NULL; + } + list_free(list); +} + +void clear_and_free_string_list(list_t * list) +{ + if (!list) { + return; + } + + listEntry_t *listEntry = NULL; + list_ForEach(listEntry, list) { + char *list_item = listEntry->data; + free(list_item); + list_item = NULL; + } + list_free(list); +} diff --git a/kubernetes/config/kube_config_util.h b/kubernetes/config/kube_config_util.h new file mode 100644 index 0000000..b5175e5 --- /dev/null +++ b/kubernetes/config/kube_config_util.h @@ -0,0 +1,100 @@ +#ifndef _KUBE_CONFIG_UTIL_H +#define _KUBE_CONFIG_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * kubeconfig_mk_cert_key_tempfile + * + * Description: + * + * Create a temporary file to persist SSL/TLS certificate or key + * + * Return: + * + * char * : File name of created temporary file + * NULL : Failed to create temporary file + * + * Parameter: + * + * IN: + * data: raw data of SSL/TLS certificate or key + * + * OUT: + * None + * + */ + char *kubeconfig_mk_cert_key_tempfile(const char *data); + +/* + * unsetSslConfig + * + * Description: + * + * unset the SSL configuration + * + * Return: + * + * None + * + * Parameter: + * + * IN: + * sslConfig: The SSL configuration + * + * OUT: + * None + * + */ + void unsetSslConfig(sslConfig_t * sslConfig); + +/* + * clear_and_free_string_pair_list + * + * Description: + * + * clear the content and free the memory for a string pair list + * + * Return: + * + * None + * + * Parameter: + * + * IN: + * list: The string pair list needs to clear and free + * + * OUT: + * None + * + */ + void clear_and_free_string_pair_list(list_t * list); + +/* + * clear_and_free_string_list + * + * Description: + * + * clear the content and free the memory for a string list + * + * Return: + * + * None + * + * Parameter: + * + * IN: + * list: The string list needs to clear and free + * + * OUT: + * None + * + */ + void clear_and_free_string_list(list_t * list); + +#ifdef __cplusplus +} +#endif +#endif /* _KUBE_CONFIG_UTIL_H */ diff --git a/kubernetes/config/kube_config_yaml.c b/kubernetes/config/kube_config_yaml.c index 949ec5e..5715225 100644 --- a/kubernetes/config/kube_config_yaml.c +++ b/kubernetes/config/kube_config_yaml.c @@ -15,9 +15,12 @@ mapping :: = MAPPING - START(node node) * MAPPING - END #define KEY_APIVERSION "apiVersion" #define KEY_KIND "kind" #define KEY_CURRENT_CONTEXT "current-context" +#define KEY_PREFERENCES "preferences" #define KEY_CLUSTERS "clusters" #define KEY_CLUSTER "cluster" #define KEY_CONTEXTS "contexts" +#define KEY_CONTEXT "context" +#define KEY_NAMESPACE "namespace" #define KEY_USERS "users" #define KEY_USER "user" #define KEY_NAME "name" @@ -27,6 +30,19 @@ mapping :: = MAPPING - START(node node) * MAPPING - END #define KEY_USER_EXEC_ENV_KEY "name" #define KEY_USER_EXEC_ENV_VALUE "value" #define KEY_USER_EXEC_ARGS "args" +#define KEY_USER_AUTH_PROVIDER "auth-provider" +#define KEY_USER_AUTH_PROVIDER_CONFIG "config" +#define KEY_USER_AUTH_PROVIDER_CONFIG_ACCESS_TOKEN "access-token" +#define KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_ID "client-id" +#define KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_SECRET "client-secret" +#define KEY_USER_AUTH_PROVIDER_CONFIG_CMD_PATH "cmd-path" +#define KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRES_ON "expires-on" +#define KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRY "expiry" +#define KEY_USER_AUTH_PROVIDER_CONFIG_ID_TOKEN "id-token" +#define KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY "idp-certificate-authority" +#define KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY_DATA "idp-certificate-authority-data" +#define KEY_USER_AUTH_PROVIDER_CONFIG_IDP_ISSUE_URL "idp-issuer-url" +#define KEY_USER_AUTH_PROVIDER_CONFIG_REFRESH_TOKEN "refresh-token" #define KEY_CERTIFICATE_AUTHORITY_DATA "certificate-authority-data" #define KEY_SERVER "server" #define KEY_CLIENT_CERTIFICATE_DATA "client-certificate-data" @@ -180,6 +196,8 @@ static int parse_kubeconfig_yaml_property_mapping(kubeconfig_property_t * proper } else if (KUBECONFIG_PROPERTY_TYPE_CONTEXT == property->type) { if (0 == strcmp(key->data.scalar.value, KEY_CLUSTER)) { property->cluster = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_NAMESPACE)) { + property->namespace = strdup(value->data.scalar.value); } else if (0 == strcmp(key->data.scalar.value, KEY_USER)) { property->user = strdup(value->data.scalar.value); } @@ -189,19 +207,58 @@ static int parse_kubeconfig_yaml_property_mapping(kubeconfig_property_t * proper } else if (0 == strcmp(key->data.scalar.value, KEY_USER_EXEC_COMMAND)) { property->command = strdup(value->data.scalar.value); } + } else if (KUBECONFIG_PROPERTY_TYPE_USER_AUTH_PROVIDER == property->type) { + if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_ACCESS_TOKEN)) { + property->access_token = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_ID)) { + property->client_id = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_SECRET)) { + property->client_secret = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_CMD_PATH)) { + property->cmd_path = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRES_ON)) { + property->expires_on = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRY)) { + property->expiry = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_ID_TOKEN)) { + property->id_token = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY)) { + property->idp_certificate_authority = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY_DATA)) { + property->idp_certificate_authority_data = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_ISSUE_URL)) { + property->idp_issuer_url = strdup(value->data.scalar.value); + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER_CONFIG_REFRESH_TOKEN)) { + property->refresh_token = strdup(value->data.scalar.value); + } } } else if (value->type == YAML_MAPPING_NODE) { - if ((KUBECONFIG_PROPERTY_TYPE_USER == property->type) && (0 == strcmp(key->data.scalar.value, KEY_USER_EXEC))) { - property->exec = kubeconfig_property_create(KUBECONFIG_PROPERTY_TYPE_USER_EXEC); - if (property->exec) { - int rc = parse_kubeconfig_yaml_property_mapping(property->exec, document, value); + if (KUBECONFIG_PROPERTY_TYPE_USER == property->type) { + int rc = 0; + if (0 == strcmp(key->data.scalar.value, KEY_USER_EXEC)) { + property->exec = kubeconfig_property_create(KUBECONFIG_PROPERTY_TYPE_USER_EXEC); + if (!property->exec) { + fprintf(stderr, "Cannot allocate memory for kubeconfig exec for user %s.\n", property->name); + return -1; + } + rc = parse_kubeconfig_yaml_property_mapping(property->exec, document, value); if (0 != rc) { fprintf(stderr, "Cannot parse kubeconfig exec for user %s.\n", property->name); return -1; } + } else if (0 == strcmp(key->data.scalar.value, KEY_USER_AUTH_PROVIDER)) { + property->auth_provider = kubeconfig_property_create(KUBECONFIG_PROPERTY_TYPE_USER_AUTH_PROVIDER); + if (!property->auth_provider) { + fprintf(stderr, "Cannot allocate memory for kubeconfig auth provider for user %s.\n", property->name); + return -1; + } + rc = parse_kubeconfig_yaml_property_mapping(property->auth_provider, document, value); + if (0 != rc) { + fprintf(stderr, "Cannot parse kubeconfig auth provider for user %s.\n", property->name); + return -1; + } } else { - fprintf(stderr, "Cannot allocate memory for kubeconfig exec for user %s.\n", property->name); - return -1; + parse_kubeconfig_yaml_property_mapping(property, document, value); } } else { parse_kubeconfig_yaml_property_mapping(property, document, value); @@ -530,3 +587,501 @@ int kubeyaml_parse_exec_crendential(ExecCredential_t * exec_credential, const ch yaml_parser_delete(&parser); return -1; } + +static int append_key_stringvalue_to_mapping_node(yaml_document_t * output_document, int parent_node, const char *key_string, const char *value_string) +{ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) key_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int value = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) value_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!value) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, value)) { + return -1; + } + return 0; +} + +int append_key_stringseq_to_mapping_node(yaml_document_t * output_document, int parent_node, const char *key_string, char **strings, int strings_count) +{ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) key_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int seq = yaml_document_add_sequence(output_document, NULL, YAML_BLOCK_SEQUENCE_STYLE); + if (!seq) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, seq)) { + return -1; + } + + for (int i = 0; i < strings_count; i++) { + int value = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) strings[i], -1, YAML_PLAIN_SCALAR_STYLE); + if (!value) { + return -1; + } + if (!yaml_document_append_sequence_item(output_document, seq, value)) { + return -1; + } + } + + return 0; +} + +int append_auth_provider_config_to_mapping_node(yaml_document_t * output_document, int parent_node, const kubeconfig_property_t * auth_provider_config) +{ + /* Add 'name': '' */ + if (auth_provider_config->name) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, parent_node, KEY_NAME, auth_provider_config->name)) { + return -1; + } + } + + /* Add 'config': {} */ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) KEY_USER_AUTH_PROVIDER_CONFIG, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int map = yaml_document_add_mapping(output_document, NULL, YAML_BLOCK_MAPPING_STYLE); + if (!map) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, map)) { + return -1; + } + + /* Add 'access-token': '' */ + if (auth_provider_config->access_token) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_ACCESS_TOKEN, auth_provider_config->access_token)) { + return -1; + } + } + + /* Add 'client-id': '' */ + if (auth_provider_config->client_id) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_ID, auth_provider_config->client_id)) { + return -1; + } + } + + /* Add 'client-secret': '' */ + if (auth_provider_config->client_secret) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_CLIENT_SECRET, auth_provider_config->client_secret)) { + return -1; + } + } + + /* Add 'cmd-path': '' */ + if (auth_provider_config->cmd_path) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_CMD_PATH, auth_provider_config->cmd_path)) { + return -1; + } + } + + /* Add 'expires-on': '' */ + if (auth_provider_config->expires_on) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRES_ON, auth_provider_config->expires_on)) { + return -1; + } + } + + /* Add 'expiry': '' */ + if (auth_provider_config->expiry) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_EXPIRY, auth_provider_config->expiry)) { + return -1; + } + } + + /* Add 'id-token': '' */ + if (auth_provider_config->id_token) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_ID_TOKEN, auth_provider_config->id_token)) { + return -1; + } + } + + /* Add 'idp-certificate-authority': '' */ + if (auth_provider_config->idp_certificate_authority) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY, auth_provider_config->idp_certificate_authority)) { + return -1; + } + } + + /* Add 'idp-certificate-authority-data': '' */ + if (auth_provider_config->idp_certificate_authority_data) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_CERTIFICATE_AUTHORITY_DATA, auth_provider_config->idp_certificate_authority_data)) { + return -1; + } + } + + /* Add 'idp-issuer-url': '' */ + if (auth_provider_config->idp_issuer_url) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_IDP_ISSUE_URL, auth_provider_config->idp_issuer_url)) { + return -1; + } + } + + /* Add 'refresh-token': '' */ + if (auth_provider_config->refresh_token) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER_CONFIG_REFRESH_TOKEN, auth_provider_config->refresh_token)) { + return -1; + } + } + + return 0; +} + +int append_key_kvpseq_to_mapping_node(yaml_document_t * output_document, int parent_node, const char *key_string, keyValuePair_t ** kvps, int kvps_count) +{ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) key_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int seq = yaml_document_add_sequence(output_document, NULL, YAML_BLOCK_SEQUENCE_STYLE); + if (!seq) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, seq)) { + return -1; + } + + for (int i = 0; i < kvps_count; i++) { + int map = yaml_document_add_mapping(output_document, NULL, YAML_BLOCK_MAPPING_STYLE); + if (!map) { + return -1; + } + if (!yaml_document_append_sequence_item(output_document, seq, map)) { + return -1; + } + + /* Add 'name': '' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_EXEC_ENV_KEY, kvps[i]->key)) { + return -1; + } + + /* Add 'value': '' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_EXEC_ENV_VALUE, kvps[i]->value)) { + return -1; + } + } + + return 0; +} + +static int append_key_map_to_mapping_node(yaml_document_t * output_document, int parent_node, const char *key_string, const kubeconfig_property_t * property) +{ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) key_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int map = yaml_document_add_mapping(output_document, NULL, YAML_BLOCK_MAPPING_STYLE); + if (!map) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, map)) { + return -1; + } + + if (!property) { + return 0; + } + + if (KUBECONFIG_PROPERTY_TYPE_CONTEXT == property->type) { + /* Add 'cluster': '' */ + if (property->cluster) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_CLUSTER, property->cluster)) { + return -1; + } + } + + /* Add 'namespace': '' */ + if (property->namespace) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_NAMESPACE, property->namespace)) { + return -1; + } + } + + /* Add 'user': '' */ + if (property->user) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER, property->user)) { + return -1; + } + } + } else if (KUBECONFIG_PROPERTY_TYPE_CLUSTER == property->type) { + /* Add 'certificate-authority-data': '' */ + if (property->certificate_authority_data) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_CERTIFICATE_AUTHORITY_DATA, property->certificate_authority_data)) { + return -1; + } + } + + /* Add 'server': '' */ + if (property->server) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_SERVER, property->server)) { + return -1; + } + } + } else if (KUBECONFIG_PROPERTY_TYPE_USER == property->type) { + /* Add 'auth-provider': {} */ + if (property->auth_provider) { + if (-1 == append_key_map_to_mapping_node(output_document, map, KEY_USER_AUTH_PROVIDER, property->auth_provider)) { + return -1; + } + } + + /* Add 'client-certificate-data': '' */ + if (property->client_certificate_data) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_CLIENT_CERTIFICATE_DATA, property->client_certificate_data)) { + return -1; + } + } + + /* Add 'client-key-data': '' */ + if (property->client_key_data) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_CLIENT_KEY_DATA, property->client_key_data)) { + return -1; + } + } + + /* Add 'exec': {} */ + if (property->exec) { + if (-1 == append_key_map_to_mapping_node(output_document, map, KEY_USER_EXEC, property->exec)) { + return -1; + } + } + } else if (KUBECONFIG_PROPERTY_TYPE_USER_EXEC == property->type) { + /* Add 'apiVersion': '' */ + if (property->apiVersion) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_APIVERSION, property->apiVersion)) { + return -1; + } + } + + /* Add 'args': [] */ + if (property->args && property->args_count > 0) { + if (-1 == append_key_stringseq_to_mapping_node(output_document, map, KEY_USER_EXEC_ARGS, property->args, property->args_count)) { + return -1; + } + } + + /* Add 'command': '' */ + if (property->command) { + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_USER_EXEC_COMMAND, property->command)) { + return -1; + } + } + + /* Add 'env': [] */ + if (property->envs && property->envs_count > 0) { + if (-1 == append_key_kvpseq_to_mapping_node(output_document, map, KEY_USER_EXEC_ENV, property->envs, property->envs_count)) { + return -1; + } + } + } else if (KUBECONFIG_PROPERTY_TYPE_USER_AUTH_PROVIDER == property->type) { + if (-1 == append_auth_provider_config_to_mapping_node(output_document, map, property)) { + return -1; + } + } + + return 0; +} + +static int append_key_seq_to_top_mapping_node(yaml_document_t * output_document, int parent_node, const char *first_level_key_string, const char *second_level_key_string, + kubeconfig_property_t ** properites, int properites_count) +{ + int key = yaml_document_add_scalar(output_document, NULL, (yaml_char_t *) first_level_key_string, -1, YAML_PLAIN_SCALAR_STYLE); + if (!key) { + return -1; + } + int seq = yaml_document_add_sequence(output_document, NULL, YAML_BLOCK_SEQUENCE_STYLE); + if (!seq) { + return -1; + } + if (!yaml_document_append_mapping_pair(output_document, parent_node, key, seq)) { + return -1; + } + + for (int i = 0; i < properites_count; i++) { + /* Add {}. */ + int map = yaml_document_add_mapping(output_document, NULL, YAML_BLOCK_MAPPING_STYLE); + if (!map) { + return -1; + } + if (!yaml_document_append_sequence_item(output_document, seq, map)) { + return -1; + } + + if (NULL != strstr(second_level_key_string, KEY_CLUSTER) || NULL != strstr(second_level_key_string, KEY_CONTEXT)) { + /* Add 'cluster/context': {} */ + if (-1 == append_key_map_to_mapping_node(output_document, map, second_level_key_string, properites[i])) { + return -1; + } + } + + /* Add 'name': '' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, map, KEY_NAME, properites[i]->name)) { + return -1; + } + + if (NULL != strstr(second_level_key_string, KEY_USER)) { + /* Add 'user': {} */ + if (-1 == append_key_map_to_mapping_node(output_document, map, second_level_key_string, properites[i])) { + return -1; + } + } + } + + return 0; +} + +static int fill_yaml_document(yaml_document_t * output_document, const kubeconfig_t * kubeconfig) +{ + int root = yaml_document_add_mapping(output_document, NULL, YAML_BLOCK_MAPPING_STYLE); + if (!root) { + return -1; + } + + /* Add 'apiVersion': '' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, root, KEY_APIVERSION, kubeconfig->apiVersion)) { + return -1; + } + + /* Add 'clusters': {} */ + if (-1 == append_key_seq_to_top_mapping_node(output_document, root, KEY_CLUSTERS, KEY_CLUSTER, kubeconfig->clusters, kubeconfig->clusters_count)) { + return -1; + } + + /* Add 'contexts': {} */ + if (-1 == append_key_seq_to_top_mapping_node(output_document, root, KEY_CONTEXTS, KEY_CONTEXT, kubeconfig->contexts, kubeconfig->contexts_count)) { + return -1; + } + + /* Add 'current-context': '' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, root, KEY_CURRENT_CONTEXT, kubeconfig->current_context)) { + return -1; + } + + /* Add 'kind': 'Config' */ + if (-1 == append_key_stringvalue_to_mapping_node(output_document, root, KEY_KIND, kubeconfig->kind)) { + return -1; + } + + /* Add 'preferences': {} */ + if (-1 == append_key_map_to_mapping_node(output_document, root, KEY_PREFERENCES, NULL)) { + return -1; + } + + /* Add 'users': {} */ + if (-1 == append_key_seq_to_top_mapping_node(output_document, root, KEY_USERS, KEY_USER, kubeconfig->users, kubeconfig->users_count)) { + return -1; + } + + return 0; +} + +int kubeyaml_save_kubeconfig(const kubeconfig_t * kubeconfig) +{ + static char fname[] = "kubeyaml_save_kubeconfig()"; + + if (!kubeconfig) { + return 0; + } + + /* Set a file output. */ + FILE *output = NULL; + if (kubeconfig->fileName) { + output = fopen(kubeconfig->fileName, "wb"); + if (!output) { + fprintf(stderr, "%s: Cannot open the file %s.[%s]\n", fname, kubeconfig->fileName, strerror(errno)); + return -1; + } + } else { + fprintf(stderr, "%s: The kubeconf file name needs be set by kubeconfig->fileName .\n", fname); + return -1; + } + + yaml_emitter_t emitter; + yaml_document_t output_document; + + memset(&emitter, 0, sizeof(emitter)); + memset(&output_document, 0, sizeof(output_document)); + + /* Initialize the emitter object. */ + if (!yaml_emitter_initialize(&emitter)) { + fprintf(stderr, "%s: Could not initialize the emitter object\n", fname); + return -1; + } + + /* Set the emitter parameters. */ + yaml_emitter_set_canonical(&emitter, 0); + yaml_emitter_set_unicode(&emitter, 1); + yaml_emitter_set_output_file(&emitter, output); + + /* Create and emit the STREAM-START event. */ + if (!yaml_emitter_open(&emitter)) { + goto emitter_error; + } + + /* Create a output_document object. */ + if (!yaml_document_initialize(&output_document, NULL, NULL, NULL, 0, 0)) { + goto document_error; + } + + if (-1 == fill_yaml_document(&output_document, kubeconfig)) { + goto document_error; + } + + /* Emit the event. */ + if (!yaml_emitter_dump(&emitter, &output_document)) { + goto emitter_error; + } + yaml_emitter_flush(&emitter); + + if (!yaml_emitter_close(&emitter)) { + goto emitter_error; + } + + yaml_emitter_delete(&emitter); + yaml_document_delete(&output_document); + fclose(output); + + return 0; + + emitter_error: + + switch (emitter.error) { + case YAML_MEMORY_ERROR: + fprintf(stderr, "%s: Memory error: Not enough memory for emitting\n", fname); + break; + + case YAML_WRITER_ERROR: + fprintf(stderr, "%s: Writer error: %s\n", fname, emitter.problem); + break; + + case YAML_EMITTER_ERROR: + fprintf(stderr, "%s: Emitter error: %s\n", fname, emitter.problem); + break; + + default: + /* Couldn't happen. */ + fprintf(stderr, "%s: Internal error\n", fname); + break; + } + + yaml_document_delete(&output_document); + yaml_emitter_delete(&emitter); + fclose(output); + + return -1; + + document_error: + + fprintf(stderr, "%s: Memory error: Not enough memory for creating a document\n", fname); + yaml_document_delete(&output_document); + yaml_emitter_delete(&emitter); + fclose(output); + + return -1; +} diff --git a/kubernetes/config/kube_config_yaml.h b/kubernetes/config/kube_config_yaml.h index 4621873..b162b5c 100644 --- a/kubernetes/config/kube_config_yaml.h +++ b/kubernetes/config/kube_config_yaml.h @@ -54,6 +54,26 @@ extern "C" { */ int kubeyaml_parse_exec_crendential(ExecCredential_t * exec_credential, const char *exec_credential_string); +/* + * kubeyaml_save_kubeconfig + * + * Description: + * + * Save the kubeconfig to the file specified by kubeconfig->fileName + * + * Return: + * + * 0 Success + * -1 Failed + * + * Parameter: + * + * INT: + * kubeconfig: kubernetes cluster configuration including kubeconfig->fileName: kubernetes cluster configuration file name + * + */ + int kubeyaml_save_kubeconfig(const kubeconfig_t * kubeconfig); + #ifdef __cplusplus } #endif /* __cplusplus */