...
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
17set -o errexit
18set -o nounset
19set -o pipefail
20
21run_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
26run_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
35apiVersion: v1
36clusters:
37- cluster:
38 name: test
39contexts:
40- context:
41 cluster: test
42 user: invalid_token_user
43 name: test
44current-context: test
45kind: Config
46preferences: {}
47users:
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
55EOF
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
86apiVersion: v1
87clusters:
88- cluster:
89 name: test
90contexts:
91- context:
92 cluster: test
93 user: valid_token_user
94 name: test
95current-context: test
96kind: Config
97preferences: {}
98users:
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
107EOF
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 -
142apiVersion: certificates.k8s.io/v1
143kind: CertificateSigningRequest
144metadata:
145 name: testuser
146spec:
147 request: $(base64 < hack/testdata/auth/testuser.csr | tr -d '\n')
148 signerName: kubernetes.io/kube-apiserver-client
149 usages: [client auth]
150EOF
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
170echo '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","status":{"clientKeyData":"bad","clientCertificateData":"bad"}}'
171EOF
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
192run_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
197run_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
206apiVersion: v1
207clusters:
208- cluster:
209 name: test
210contexts:
211- context:
212 cluster: test
213 user: always_interactive_token_user
214 name: test
215current-context: test
216kind: Config
217preferences: {}
218users:
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
227EOF
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
246set -o errexit
247set -o nounset
248set -o pipefail
249kubectl ${kube_flags_without_token[*]:?} --kubeconfig=${TMPDIR:-/tmp}/always_interactive_exec_plugin.yaml ${kubectl_command} 2>&1 || true
250EOF
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
279apiVersion: v1
280clusters:
281- cluster:
282 name: test
283contexts:
284- context:
285 cluster: test
286 user: missing_interactive_token_user
287 name: test
288current-context: test
289kind: Config
290preferences: {}
291users:
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"}}'
299EOF
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}
View as plain text