k8s.io/kubernetes@v1.29.3/test/cmd/authentication.sh (about)

     1  #!/usr/bin/env bash
     2  
     3  # Copyright 2020 The Kubernetes Authors.
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  set -o errexit
    18  set -o nounset
    19  set -o pipefail
    20  
    21  run_exec_credentials_tests() {
    22    run_exec_credentials_tests_version client.authentication.k8s.io/v1beta1
    23    run_exec_credentials_tests_version client.authentication.k8s.io/v1
    24  }
    25  
    26  run_exec_credentials_tests_version() {
    27    set -o nounset
    28    set -o errexit
    29  
    30    local -r apiVersion="$1"
    31  
    32    kube::log::status "Testing kubectl with configured ${apiVersion} exec credentials plugin"
    33  
    34    cat > "${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml << EOF
    35  apiVersion: v1
    36  clusters:
    37  - cluster:
    38    name: test
    39  contexts:
    40  - context:
    41      cluster: test
    42      user: invalid_token_user
    43    name: test
    44  current-context: test
    45  kind: Config
    46  preferences: {}
    47  users:
    48  - name: invalid_token_user
    49    user:
    50      exec:
    51        apiVersion: ${apiVersion}
    52        # Any invalid exec credential plugin will do to demonstrate
    53        command: ls
    54        interactiveMode: IfAvailable
    55  EOF
    56  
    57    ### Provided --token should take precedence, thus not triggering the (invalid) exec credential plugin
    58    # Pre-condition: Client certificate authentication enabled on the API server
    59    kube::util::test_client_certificate_authentication_enabled
    60    # Command
    61    output=$(kubectl "${kube_flags_with_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml get namespace kube-system -o name || true)
    62  
    63    if [[ "${output}" == "namespace/kube-system" ]]; then
    64      kube::log::status "exec credential plugin not triggered since kubectl was called with provided --token"
    65    else
    66      kube::log::status "Unexpected output when providing --token for authentication - exec credential plugin likely triggered. Output: ${output}"
    67      exit 1
    68    fi
    69    # Post-condition: None
    70  
    71    ### Without provided --token, the exec credential plugin should be triggered
    72    # Pre-condition: Client certificate authentication enabled on the API server - already checked by positive test above
    73  
    74    # Command
    75    output2=$(kubectl "${kube_flags_without_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml get namespace kube-system -o name 2>&1 || true)
    76  
    77    if [[ "${output2}" =~ "json parse error" ]]; then
    78      kube::log::status "exec credential plugin triggered since kubectl was called without provided --token"
    79    else
    80      kube::log::status "Unexpected output when not providing --token for authentication - exec credential plugin not triggered. Output: ${output2}"
    81      exit 1
    82    fi
    83    # Post-condition: None
    84  
    85    cat >"${TMPDIR:-/tmp}"/valid_exec_plugin.yaml <<EOF
    86  apiVersion: v1
    87  clusters:
    88  - cluster:
    89    name: test
    90  contexts:
    91  - context:
    92      cluster: test
    93      user: valid_token_user
    94    name: test
    95  current-context: test
    96  kind: Config
    97  preferences: {}
    98  users:
    99  - name: valid_token_user
   100    user:
   101      exec:
   102        apiVersion: ${apiVersion}
   103        command: echo
   104        args:
   105          - '{"apiVersion":"${apiVersion}","status":{"token":"admin-token"}}'
   106        interactiveMode: IfAvailable
   107  EOF
   108  
   109    ### Valid exec plugin should authenticate user properly
   110    # Pre-condition: Client certificate authentication enabled on the API server - already checked by positive test above
   111  
   112    # Command
   113    output3=$(kubectl "${kube_flags_without_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/valid_exec_plugin.yaml get namespace kube-system -o name 2>&1 || true)
   114  
   115    if [[ "${output3}" == "namespace/kube-system" ]]; then
   116      kube::log::status "exec credential plugin triggered and provided valid credentials"
   117    else
   118      kube::log::status "Unexpected output when using valid exec credential plugin for authentication. Output: ${output3}"
   119      exit 1
   120    fi
   121    # Post-condition: None
   122  
   123    ### Provided --username/--password should take precedence, thus not triggering the (valid) exec credential plugin
   124    # Pre-condition: Client certificate authentication enabled on the API server - already checked by positive test above
   125  
   126    # Command
   127    output4=$(kubectl "${kube_flags_without_token[@]:?}" --username bad --password wrong --kubeconfig="${TMPDIR:-/tmp}"/valid_exec_plugin.yaml get namespace kube-system -o name 2>&1 || true)
   128  
   129    if [[ "${output4}" =~ "Unauthorized" ]]; then
   130      kube::log::status "exec credential plugin not triggered since kubectl was called with provided --username/--password"
   131    else
   132      kube::log::status "Unexpected output when providing --username/--password for authentication - exec credential plugin likely triggered. Output: ${output4}"
   133      exit 1
   134    fi
   135    # Post-condition: None
   136  
   137    ### Provided --client-certificate/--client-key should take precedence on the cli, thus not triggering the (invalid) exec credential plugin
   138    # contained in the kubeconfig.
   139  
   140    # Use CSR to get a valid certificate
   141    cat <<EOF | kubectl create -f -
   142  apiVersion: certificates.k8s.io/v1
   143  kind: CertificateSigningRequest
   144  metadata:
   145    name: testuser
   146  spec:
   147    request: $(base64 < hack/testdata/auth/testuser.csr | tr -d '\n')
   148    signerName: kubernetes.io/kube-apiserver-client
   149    usages: [client auth]
   150  EOF
   151  
   152    kube::test::wait_object_assert 'csr/testuser' '{{range.status.conditions}}{{.type}}{{end}}' ''
   153    kubectl certificate approve testuser
   154    kube::test::wait_object_assert 'csr/testuser' '{{range.status.conditions}}{{.type}}{{end}}' 'Approved'
   155    # wait for certificate to not be empty
   156    kube::test::wait_object_assert 'csr/testuser' '{{.status.certificate}}' '.+'
   157    kubectl get csr testuser -o jsonpath='{.status.certificate}' | base64 -d > "${TMPDIR:-/tmp}"/testuser.crt
   158  
   159    output5=$(kubectl  "${kube_flags_without_token[@]:?}" --client-certificate="${TMPDIR:-/tmp}"/testuser.crt --client-key="hack/testdata/auth/testuser.key" --kubeconfig="${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml get namespace kube-system -o name)
   160    if [[ "${output5}" =~ "Unauthorized" ]]; then
   161      kube::log::status "Unexpected output when providing --client-certificate/--client-key for authentication - exec credential plugin likely triggered. Output: ${output5}"
   162      exit 1
   163    else
   164      kube::log::status "exec credential plugin not triggered since kubectl was called with provided --client-certificate/--client-key"
   165    fi
   166  
   167      ### Provided --client-certificate/--client-key should take precedence in the kubeconfig, thus not triggering the (invalid) exec credential plugin.
   168    cat >"${TMPDIR:-/tmp}"/invalid_execcredential.sh <<EOF
   169  #!/bin/bash
   170  echo '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","status":{"clientKeyData":"bad","clientCertificateData":"bad"}}'
   171  EOF
   172    chmod +x "${TMPDIR:-/tmp}"/invalid_execcredential.sh
   173  
   174    kubectl config set-credentials testuser --client-certificate="${TMPDIR:-/tmp}"/testuser.crt --client-key="hack/testdata/auth/testuser.key" --exec-api-version=client.authentication.k8s.io/v1beta1 --exec-command=/tmp/invalid_execcredential.sh
   175    output6=$(kubectl  "${kube_flags_without_token[@]:?}" --user testuser get namespace kube-system -o name)
   176    if [[ "${output6}" =~ "Unauthorized" ]]; then
   177      kube::log::status "Unexpected output when kubeconfig was configured with --client-certificate/--client-key for authentication - exec credential plugin likely triggered. Output: ${output6}"
   178      exit 1
   179    else
   180      kube::log::status "exec credential plugin not triggered since kubeconfig was configured with --client-certificate/--client-key for authentication"
   181    fi
   182  
   183    kubectl delete csr testuser
   184    rm "${TMPDIR:-/tmp}"/invalid_execcredential.sh
   185    rm "${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml
   186    rm "${TMPDIR:-/tmp}"/valid_exec_plugin.yaml
   187  
   188    set +o nounset
   189    set +o errexit
   190  }
   191  
   192  run_exec_credentials_interactive_tests() {
   193    run_exec_credentials_interactive_tests_version client.authentication.k8s.io/v1beta1
   194    run_exec_credentials_interactive_tests_version client.authentication.k8s.io/v1
   195  }
   196  
   197  run_exec_credentials_interactive_tests_version() {
   198    set -o nounset
   199    set -o errexit
   200  
   201    local -r apiVersion="$1"
   202  
   203    kube::log::status "Testing kubectl with configured ${apiVersion} interactive exec credentials plugin"
   204  
   205    cat >"${TMPDIR:-/tmp}"/always_interactive_exec_plugin.yaml <<EOF
   206  apiVersion: v1
   207  clusters:
   208  - cluster:
   209    name: test
   210  contexts:
   211  - context:
   212      cluster: test
   213      user: always_interactive_token_user
   214    name: test
   215  current-context: test
   216  kind: Config
   217  preferences: {}
   218  users:
   219  - name: always_interactive_token_user
   220    user:
   221      exec:
   222        apiVersion: ${apiVersion}
   223        command: echo
   224        args:
   225          - '{"apiVersion":"${apiVersion}","status":{"token":"admin-token"}}'
   226        interactiveMode: Always
   227  EOF
   228  
   229    ### The exec credential plugin should not be run if it kubectl already uses standard input
   230    # Pre-condition: The kubectl command requires standard input
   231  
   232    some_resource='{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"some-resource"}}'
   233  
   234    # Declare map from kubectl command to standard input data
   235    declare -A kubectl_commands
   236    kubectl_commands["apply -f -"]="$some_resource"
   237    kubectl_commands["set env deployment/some-deployment -"]="SOME_ENV_VAR_KEY=SOME_ENV_VAR_VAL"
   238    kubectl_commands["replace -f - --force"]="$some_resource"
   239  
   240    failure=
   241    for kubectl_command in "${!kubectl_commands[@]}"; do
   242      # Use a separate bash script for the command here so that script(1) will not get confused with kubectl flags
   243      script_file="${TMPDIR:-/tmp}/test-cmd-exec-credentials-script-file.sh"
   244      cat <<EOF >"$script_file"
   245  #!/usr/bin/env bash
   246  set -o errexit
   247  set -o nounset
   248  set -o pipefail
   249  kubectl ${kube_flags_without_token[*]:?} --kubeconfig=${TMPDIR:-/tmp}/always_interactive_exec_plugin.yaml ${kubectl_command} 2>&1 || true
   250  EOF
   251      chmod +x "$script_file"
   252  
   253      # Run kubectl as child of script(1) so kubectl will always run with a PTY
   254      # Dynamically build script(1) command so that we can conditionally add flags on Linux
   255      script_command="script -q /dev/null"
   256      if [[ "$(uname)" == "Linux" ]]; then script_command="${script_command} -c"; fi
   257      script_command="${script_command} ${script_file}"
   258  
   259      # Specify SHELL env var when we call script(1) since it is picky about the format of the env var
   260      shell="$(which bash)"
   261  
   262      kube::log::status "Running command '$script_command' (kubectl command: '$kubectl_command') with input '${kubectl_commands[$kubectl_command]}'"
   263      output=$(echo "${kubectl_commands[$kubectl_command]}" | SHELL="$shell" $script_command)
   264  
   265      if [[ "${output}" =~ "used by stdin resource manifest reader" ]]; then
   266        kube::log::status "exec credential plugin not run because kubectl already uses standard input"
   267      else
   268        kube::log::status "Unexpected output when running kubectl command that uses standard input. Output: ${output}"
   269        failure=yup
   270      fi
   271    done
   272  
   273    if [[ -n "$failure" ]]; then
   274      exit 1
   275    fi
   276    # Post-condition: None
   277  
   278    cat >"${TMPDIR:-/tmp}"/missing_interactive_exec_plugin.yaml <<EOF
   279  apiVersion: v1
   280  clusters:
   281  - cluster:
   282    name: test
   283  contexts:
   284  - context:
   285      cluster: test
   286      user: missing_interactive_token_user
   287    name: test
   288  current-context: test
   289  kind: Config
   290  preferences: {}
   291  users:
   292  - name: missing_interactive_token_user
   293    user:
   294      exec:
   295        apiVersion: ${apiVersion}
   296        command: echo
   297        args:
   298          - '{"apiVersion":"${apiVersion}","status":{"token":"admin-token"}}'
   299  EOF
   300  
   301    ### The kubeconfig will fail to be loaded if a v1 exec credential plugin is missing an interactiveMode field, otherwise it will default to IfAvailable
   302    # Pre-condition: The exec credential plugin is missing an interactiveMode setting
   303  
   304    output2=$(kubectl "${kube_flags_without_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/missing_interactive_exec_plugin.yaml get namespace kube-system -o name 2>&1 || true)
   305  
   306    if [[ "$apiVersion" == "client.authentication.k8s.io/v1" ]]; then
   307      if [[ "${output2}" =~ "error: interactiveMode must be specified for missing_interactive_token_user to use exec authentication plugin" ]]; then
   308        kube::log::status "kubeconfig was not loaded successfully because ${apiVersion} exec credential plugin is missing interactiveMode"
   309      else
   310        kube::log::status "Unexpected output when running kubectl command that uses a ${apiVersion} exec credential plugin without an interactiveMode. Output: ${output2}"
   311        exit 1
   312      fi
   313    else
   314      if [[ "${output2}" == "namespace/kube-system" ]]; then
   315        kube::log::status "${apiVersion} exec credential plugin triggered and provided valid credentials"
   316      else
   317        kube::log::status "Unexpected output when using valid exec credential plugin for authentication. Output: ${output2}"
   318        exit 1
   319      fi
   320    fi
   321    # Post-condition: None
   322  
   323    rm "${TMPDIR:-/tmp}"/always_interactive_exec_plugin.yaml
   324    rm "${TMPDIR:-/tmp}"/missing_interactive_exec_plugin.yaml
   325  
   326    set +o nounset
   327    set +o errexit
   328  }