From 26fa30cef28666aae6ad8b7e24f8c99f83787197 Mon Sep 17 00:00:00 2001 From: Kazuki Suda <230185+superbrothers@users.noreply.github.com> Date: Tue, 30 Oct 2018 13:29:21 +0900 Subject: [PATCH] Merge kubeconfig files using "kubectl config view" (#51) * Revert "Revert "Use token with kubeconfig set (#46)" (#48)" This reverts commit 9112a93947b3dec8ee77cf2080dc8c734fd19401. * Merge kubeconfig files using "kubectl config view" When using `use_aws_iam_authenticator`, the kubeconfig file is broken due to merging by shell redirection. With this commit, the kubeconfig file is merged by `kubectl config view` command instead of shell redirection. --- README.md | 4 ++-- assets/common.sh | 61 ++++++++++++++++++++++++++++++------------------ test/helper.bash | 8 +++++++ test/suite.bats | 29 +++++++++++++++++++++++ 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d4f4f33..973ee4e 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ The version of this resource corresponds to the version of kubectl. We recommend ### cluster configs -- `server`: *Optional.* The address and port of the API server. Requires `token`. -- `token`: *Optional.* Bearer token for authentication to the API server. Requires `server`. +- `server`: *Optional.* The address and port of the API server. +- `token`: *Optional.* Bearer token for authentication to the API server. - `namespace`: *Optional.* The namespace scope. Defaults to `default`. If set along with `kubeconfig`, `namespace` will override the namespace in the current-context - `certificate_authority`: *Optional.* A certificate file for the certificate authority. ```yaml diff --git a/assets/common.sh b/assets/common.sh index 9cfe184..f7c4ae6 100644 --- a/assets/common.sh +++ b/assets/common.sh @@ -11,6 +11,13 @@ setup_kubectl() { local payload payload=$1 + # the entry name for auth of kubeconfig + local -r AUTH_NAME=auth + # the entry name for cluster of kubeconfig + local -r CLUSTER_NAME=cluster + # the entry name for context of kubeconfig + local -r CONTEXT_NAME=kubernetes-resource + KUBECONFIG="$(mktemp "$TMPDIR/kubernetes-resource-kubeconfig.XXXXXX")" export KUBECONFIG @@ -34,9 +41,6 @@ setup_kubectl() { # Optional. The address and port of the API server. Requires token. local server server="$(jq -r '.source.server // ""' < "$payload")" - # Optional. Bearer token for authentication to the API server. Requires server. - local token - token="$(jq -r '.source.token // ""' < "$payload")" # Optional. A certificate file for the certificate authority. local certificate_authority certificate_authority="$(jq -r '.source.certificate_authority // ""' < "$payload")" @@ -45,23 +49,6 @@ setup_kubectl() { local insecure_skip_tls_verify insecure_skip_tls_verify="$(jq -r '.source.insecure_skip_tls_verify // ""' < "$payload")" - if [[ -z "$server" || -z "$token" ]]; then - echoerr 'You must specify "server" and "token", if not specify "kubeconfig".' - exit 1 - fi - - local -r AUTH_NAME=auth - local -r CLUSTER_NAME=cluster - local -r CONTEXT_NAME=kubernetes-resource - - # Build options for kubectl config set-credentials - # Avoid to expose the token string by using placeholder - local set_credentials_opts - set_credentials_opts=("--token=**********") - exe kubectl config set-credentials "$AUTH_NAME" "${set_credentials_opts[@]}" - # placeholder is replaced with actual token string - sed -i -e "s/[*]\\{10\\}/$token/" "$KUBECONFIG" - # Build options for kubectl config set-cluster local set_cluster_opts set_cluster_opts=("--server=$server") @@ -76,7 +63,7 @@ setup_kubectl() { fi exe kubectl config set-cluster "$CLUSTER_NAME" "${set_cluster_opts[@]}" - exe kubectl config set-context "$CONTEXT_NAME" --user="$AUTH_NAME" --cluster="$CLUSTER_NAME" + exe kubectl config set-context "$CONTEXT_NAME" --cluster="$CLUSTER_NAME" exe kubectl config use-context "$CONTEXT_NAME" @@ -90,14 +77,26 @@ setup_kubectl() { echoerr 'You must specify aws_eks_cluster_name when using aws_iam_authenticator.' exit 1 fi - echo " exec: + local kubeconfig_file_aws + kubeconfig_file_aws="$(mktemp "$TMPDIR/kubernetes-resource-kubeconfig-aws.XXXXXX")" + cat < "$kubeconfig_file_aws" +users: +- name: ${AUTH_NAME} + user: + exec: apiVersion: client.authentication.k8s.io/v1alpha1 args: - token - -i - ${aws_eks_cluster_name} command: aws-iam-authenticator - env: null" >> "$KUBECONFIG" + env: null +EOF + # Merge two kubeconfig files + local tmpfile + tmpfile="$(mktemp)" + KUBECONFIG="$KUBECONFIG:$kubeconfig_file_aws" kubectl config view --flatten > "$tmpfile" + cat "$tmpfile" > "$KUBECONFIG" fi fi @@ -111,6 +110,22 @@ setup_kubectl() { if [[ -n "$namespace" ]]; then exe kubectl config set-context "$(kubectl config current-context)" --namespace="$namespace" fi + + # if providing a token we set a user and override context to support both kubeconfig and generated config + local token + token="$(jq -r '.source.token // ""' < "$payload")" + if [[ -n "$token" ]]; then + # Build options for kubectl config set-credentials + # Avoid to expose the token string by using placeholder + local set_credentials_opts + set_credentials_opts=("--token=**********") + exe kubectl config set-credentials "$AUTH_NAME" "${set_credentials_opts[@]}" + # placeholder is replaced with actual token string + sed -i -e "s/[*]\\{10\\}/$token/" "$KUBECONFIG" + + # override user of context to one with token + exe kubectl config set-context "$(kubectl config current-context)" --user="$AUTH_NAME" + fi # Optional. The name of the kubeconfig context to use. local context diff --git a/test/helper.bash b/test/helper.bash index cfe5fae..7439aad 100644 --- a/test/helper.bash +++ b/test/helper.bash @@ -39,3 +39,11 @@ assert_match() { return 1 fi } + +assert_not_match() { + if [[ "$2" =~ $1 ]]; then + echo "expected: $1" + echo "actual: $2" + return 1 + fi +} diff --git a/test/suite.bats b/test/suite.bats index 3de4eb8..fcc2db2 100644 --- a/test/suite.bats +++ b/test/suite.bats @@ -12,6 +12,17 @@ setup() { kubectl config view --flatten --minify > "$kubeconfig_file" # Change the current-context to $namespace kubectl --kubeconfig "$kubeconfig_file" config set-context ${current_context} --namespace "$namespace" + # Create a kubeconfig json without users (no token) + kubeconfig_file_no_token="$(mktemp)" + kubectl config view --flatten --minify -o json | jq -r 'del(.contexts[0].context.user,.users)' > "$kubeconfig_file_no_token" + # create rolebinding for full namespace access to default service account in namespace to avoid forbidden errors with token + kubectl create -n $namespace rolebinding --clusterrole=cluster-admin --serviceaccount=$namespace:default testaccount + # get default service account + serviceaccount=$(kubectl get -n $namespace serviceaccount default -o json | jq -r '.secrets[0].name') + # Extract server from service account for testing + server="$(kubectl get -n $namespace secret "$serviceaccount" -o json | jq -r '.data["server"]' | base64 -d)" + # Extract token from service account for testing + token="$(kubectl get -n $namespace secret "$serviceaccount" -o json | jq -r '.data["token"]' | base64 -d)" } teardown() { @@ -21,6 +32,14 @@ teardown() { rm "$kubeconfig_file" } +@test "with outputs.use_aws_iam_authenticator" { + run assets/out <<< "$(jq -n '{"source": {"use_aws_iam_authenticator": true, "aws_eks_cluster_name": "eks-cluster01", "server": $server, "token": $token}, "params": {"kubectl": "get po"}}' \ + --arg server "$server" \ + --arg token "$token" \ + --arg kubectl "get po nginx")" + assert_not_match 'did not find expected key' "$output" +} + @test "with source.kubeconfig" { run assets/out <<< "$(jq -n '{"source": {"kubeconfig": $kubeconfig}, "params": {"kubectl": $kubectl}}' \ --arg kubeconfig "$(cat "$kubeconfig_file")" \ @@ -57,6 +76,16 @@ teardown() { assert_failure } +@test "with no credentials in outputs.kubeconfig_file and source.token" { + run assets/out <<< "$(jq -n '{"source": {"token": $token}, "params": {"kubectl": $kubectl, "kubeconfig_file": $kubeconfig_file, "namespace": $namespace}}' \ + --arg token "$token" \ + --arg kubeconfig_file "$kubeconfig_file_no_token" \ + --arg kubectl "get ns $namespace -o name" \ + --arg namespace "$namespace")" + assert_match "namespace/$namespace" "$output" + assert_success +} + @test "command substitution in outputs.kubectl" { run kubectl --kubeconfig "$kubeconfig_file" run nginx --image=nginx assert_success