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 }