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

[Websocket] Support exec #65

Merged
merged 1 commit into from
Jul 16, 2021

Conversation

ityuhui
Copy link
Member

@ityuhui ityuhui commented Jun 28, 2021

websocket and pod exec support for the c client

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Jun 28, 2021
@k8s-ci-robot k8s-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Jun 28, 2021
@ityuhui
Copy link
Member Author

ityuhui commented Jun 28, 2021

/cc @brendandburns

README.md Outdated Show resolved Hide resolved
@@ -16,6 +16,15 @@ CLIENT_REPO_ROOT=${PWD}/c
# Install pre-requisites
sudo apt-get install libssl-dev libcurl4-openssl-dev uncrustify libyaml-dev

# Build pre-requisite: libwebsockets
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to build from source? Or just apt-get install libwebsockets-dev?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need build from source for libwebsockets now.

Because the code in this PR uses the latest stable version v4.2, but the version of libwebsockets-dev in Ubuntu 20.04 LTS (docker image ubuntu-latest) is v3.2.1, which is not compatible.

Users using Ubuntu 20.10 or above can install libwebsockets-dev v4.x by

apt-get install libwebsockets-dev

We can update it after Ubuntu 22.04 LTS releases next year.

printf("%s: Received %ld bytes:\n%s", __func__, *p_data_received_len, (char *) (*p_data_received));
}

static void escape_character_in_url(char *escaped, int escaped_buffer_size, const char chr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use curl_easy_escape(...) instead?

https://curl.se/libcurl/c/curl_easy_escape.html

Copy link
Member Author

@ityuhui ityuhui Jun 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escape_character_in_url is different with curl_easy_escape, the former receives only one character e.g. + and then returns an escaped string, e.g. %2B at one time. But curl_easy_escape receives a string and returns the full escaped string for the whole url string.

At the case of kube exec, we need handle the characters one by one because the space (" ") should not be escaped, it needs to be converted to "&command=", e.g.
"ls /" will be converted to
"ls&command=/"

curl_easy_escape cannot do this.

}
}

static int assemble_command_in_url(char *command_string_in_url, int url_command_string_buffer_size, const char *original_command_string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If url_command_string_buffer_size is > ESCAPED_STRING_BUFFER_SIZE this will overflow the escaped_string buffer. We should test for this and return -1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function escape_character_in_url is different with curl_easy_escape in libcurl.

ESCAPED_STRING_BUFFER_SIZE is not the size of the whole escaped string of url, it is the size of string that only one character escaped, e.g. + escapes to %2B (length is 3) , actually ESCAPED_STRING_BUFFER_SIZE=4 is enough.

I have test command_string_length in function to prevent the command_string_in_url from overflowing

if (command_string_length >= url_command_string_buffer_size) {

Copy link
Contributor

@brendandburns brendandburns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments.

@ityuhui
Copy link
Member Author

ityuhui commented Jun 29, 2021

Thank you @brendandburns
I will address these comments one by one.

free(wsc);
}

static struct lws_context *g_lws_context;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should g_lws_context be a global variable?
Do we have to maintain ws context if I run a command with kube_exec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the usage in the samples in libweboscket repo.

Do you concern whether it can work in multi-thread env ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. running in the multithread env, the static variable is not safe.

Copy link
Member Author

@ityuhui ityuhui Jul 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't investigate this but some searching results show multi-thread is not supported easily by libwebsockets.

For the first commit of the feature, I will not consider this case. Welcome do some research for this!

For now, the case that only one thread runs "kube_exec", and other threads use apiClient_t (libcurl) is OK.

i.context = g_lws_context;
i.port = wsc->server_port;
i.address = wsc->server_address;
i.path = wsc->path;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it wss:// connection? or ws:// connection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the value of i.ssl_connection

    i.ssl_connection = wsc->ssl_config ? LCCSCF_USE_SSL : 0;

@ityuhui
Copy link
Member Author

ityuhui commented Jun 30, 2021

Hi @brendandburns

Review comments are addressed and replied.

Could you please take a look again ?

@brendandburns
Copy link
Contributor

Thanks for addressing the comments.

I had one clarification.

Also, I think that the escaping of the chars code is a little complicated, I would suggest that we pre-allocate the entire escaped string and then run through it linearly rather than using strncat, also I think it needs unit tests, but we can address that in a follow up PR.

If we clean up the unnecessary allocation by changing the ownership of the path variable, then I'm ok with merging.

@ityuhui
Copy link
Member Author

ityuhui commented Jul 4, 2021

Thanks @brendandburns .

I will address the comments after coming back from vacation.

@brendandburns
Copy link
Contributor

Have a good vacation!

info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
info.protocols = protocols;
info.fd_limit_per_thread = 1 + 1 + 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this expression vs setting the limit to just 3?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code comes from the sample of libwebsokets and it has below explanation for users :

        /*
         * since we know this lws context is only ever going to be used with
         * one client wsis / fds / sockets at a time, let lws know it doesn't
         * have to use the default allocations for fd tables up to ulimit -n.
         * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
         * will use.
         */
        info.fd_limit_per_thread = 1 + 1 + 1;

#define TTY_STDIN_NUMBER 0
#define TTY_STDOUT_NUMBER 1
#define WS_PROTOCOL_DELIM "://"
#define WS_BASE_PATH_DELIM ":" /* ip:port */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't clarify the port in IPV6 address
for example, if address is [fd04::1]:443
the port is not distinguished.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a problem in ipv6 env.
But the delim ":" is right even in the format [fd04::1]:443.
The fix should use strrchr in the function of get_server_address_and_port.
I will do it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const char *port_string = p + strlen(WS_BASE_PATH_DELIM);

int ws_port = atoi(port_string);
if (0 == ws_port) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use strtol intead of using atoi
atoi is unsafe function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is atoi unsafe ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik, atoi("some-non-numeric-string") is undefined in the C spec, so the strto... family are preferred.

https://wiki.sei.cmu.edu/confluence/display/c/ERR34-C.+Detect+errors+when+converting+a+string+to+a+number

I don't think it matters too much in this case, but it's a good habit to get into.

char *base_path = NULL;
sslConfig_t *ssl_config = NULL;
list_t *api_keys = NULL;
int rc = load_kube_config(&base_path, &ssl_config, &api_keys, NULL); /* NULL means loading configuration from $HOME/.kube/config */

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test with load_incluster_config?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR cannot support running in cluster now.
We may support it in future. ^-^

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling with not establishing ssl connection while using load_incluster_config.
Do you know what kind of missing implementation will it to fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ssl_config" returning from load_incluster_config only has CA file but does not have the client certificate and private key (which are not provided in cluster) so it cannot pass authentication by Kubernetes API server.

"api_keys" holding the authentication credential should be passed to the websocket client. And the websocket client needs updating to use this credential.

@ityuhui ityuhui force-pushed the yh-websocket-exec branch 5 times, most recently from 7b289c6 to be31e23 Compare July 13, 2021 13:19
@ityuhui ityuhui force-pushed the yh-websocket-exec branch 10 times, most recently from 8217811 to b3b8562 Compare July 14, 2021 10:30
@ityuhui
Copy link
Member Author

ityuhui commented Jul 14, 2021

Hi @brendandburns

I have made below changes:

  1. Removed the ownership of path in wsclient_t
  2. Add the parameter apiKeys to the function
wsclient_t *wsclient_create(const char *base_path, sslConfig_t * ssl_config, list_t * apiKeys, int ws_log_mask)

This parameter is not used now, but for in-cluster authentication in future.

Could you please check whether this PR can merge now ? Thanks.

@brendandburns
Copy link
Contributor

@ityuhui this code looks ok to me, but I'd like to make sure that @clearday4 is ok with it also before we merge.

Thanks!

@clearday4
Copy link

@ityuhui @brendanburns
it is good in first stage.
In the next step, It's better to consider the multithread and implement an additional LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER callback for incluster_config.
Thanks a lot!

lwsl_user("%s: wsclient connection established\n", __func__);
lws_callback_on_writable(wsi);
break;

Copy link

@clearday4 clearday4 Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the incluster_config
I add the this case for adding token to header

    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
    {
        const char *tpath = "/var/run/secrets/kubernetes.io/serviceaccount/token";
        char *token = read_complete_file(tpath);  // using internal function
        char *token_header = strcat_copy("Bearer ", token);   // using internal function
        const char *acc = "*/*";

        unsigned char **p = (unsigned char **)in, *end = (*p) + len;

        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT,
                (unsigned char *)acc, (int)strlen(acc), p, end))
        {
            free(token_header);
            return -1;
        }

        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_AUTHORIZATION,
                (unsigned char *)token_header, (int)strlen(token_header), p, end))
        {
            free(token_header);
            return -1;
        }

        free(token_header);
        break;
    }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It's helpful.

@brendandburns
Copy link
Contributor

/lgtm
/approve

We can merge this and take improvements as the next step.

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jul 16, 2021
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: brendandburns, ityuhui

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [brendandburns,ityuhui]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot merged commit d83ca5a into kubernetes-client:master Jul 16, 2021
@ityuhui ityuhui deleted the yh-websocket-exec branch July 17, 2021 03:58
@ityuhui
Copy link
Member Author

ityuhui commented Jul 17, 2021

Thank you for your review @brendandburns @clearday4

@ityuhui ityuhui mentioned this pull request Aug 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants