Skip to content

Commit b423216

Browse files
committed
Presence of bearer token should cancel exec action
If a bearer token is present in a request, the exec credential plugin should accept that as the chosen method of authentication. Judging by an [earlier comment in exec.go](https://github.com/kubernetes/kubernetes/blob/c18bc7e9f7a27d7d197053d98c52896e1dc3bb3e/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go#L217), this was already intended. This would however not work since UpdateTransportConfig would set the GetCert callback which would then get called by the transport, triggering the exec plugin action even with a token present in the request. See linked issue for further details. See kubernetes#87369 for further details. Signed-off-by: Anders Eknert <[email protected]>
1 parent a138be8 commit b423216

File tree

7 files changed

+153
-0
lines changed

7 files changed

+153
-0
lines changed

hack/lib/util.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,22 @@ function kube::util::test_openssl_installed {
452452
OPENSSL_BIN=$(command -v openssl)
453453
}
454454

455+
# Query the API server for client certificate authentication capabilities
456+
function kube::util::test_client_certificate_authentication_enabled {
457+
local output
458+
kube::util::test_openssl_installed
459+
460+
output=$(echo \
461+
| "${OPENSSL_BIN}" s_client -connect "127.0.0.1:${SECURE_API_PORT}" 2> /dev/null \
462+
| grep -A3 'Acceptable client certificate CA names')
463+
464+
if [[ "${output}" != *"/CN=127.0.0.1"* ]] && [[ "${output}" != *"CN = 127.0.0.1"* ]]; then
465+
echo "API server not configured for client certificate authentication"
466+
echo "Output of from acceptable client certificate check: ${output}"
467+
exit 1
468+
fi
469+
}
470+
455471
# creates a client CA, args are sudo, dest-dir, ca-id, purpose
456472
# purpose is dropped in after "key encipherment", you usually want
457473
# '"client auth"'

hack/make-rules/test-cmd.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function run_kube_apiserver() {
6969
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
7070
--cert-dir="${TMPDIR:-/tmp/}" \
7171
--service-cluster-ip-range="10.0.0.0/24" \
72+
--client-ca-file=hack/testdata/ca.crt \
7273
--token-auth-file=hack/testdata/auth-tokens.csv 1>&2 &
7374
export APISERVER_PID=$!
7475

hack/testdata/ca.crt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICpjCCAY4CCQCZBiNB23olFzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAkx
3+
MjcuMC4wLjEwIBcNMjAwNjE0MTk0OTM4WhgPMjI5NDAzMzAxOTQ5MzhaMBQxEjAQ
4+
BgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
5+
AMxIjMd58IhiiyK4VjmuCWBUZksSs1CcQuo5HSpOqogVZ+vR5mdJDZ56Pw/NSM5c
6+
RqOB3cvjGrxYQe/lKvo9D3UmWLcRKtxdlWxCfPekioJ25/dhGOxtBQcjtp/TSqTM
7+
txprwT4fvsVwiwaURFoCOivF4xjQFG0K1i3/m7CiMHODy67M1EfJDrM7Vv5XPIuJ
8+
VF8HhWBH2HiM25ak34XhxVTX8K97k6wO9OZ5GMqbYuVobTZrSRdiv8s95rkmik6P
9+
jn0ePKqSz6cXNXgXqTl11WtsuoGgjOdB8j/noqTF3m3z17sSBqqG/xBFuSFoNceA
10+
yBDb9ohbs8oY3NIZzyMrt8MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFgcaqRgv
11+
qylx4ogL5iUr0K2e/8YzsvH7zLHG6xnr7HxpR/p0lQt3dPlppECZMGDKElbCgU8f
12+
xVDdZ3FOxHTJ51Vnq/U5xJo+UOMJ4sS8fEH8cfNliSsvmSKzjxpPKqbCJ7VTnkW8
13+
lonedCPRksnhlD1U8CF21rEjKsXcLoX5PsxlS4DX3PtO0+e8aUh9F4XyZagpejq8
14+
0ttXkWd3IyYrpFRGDlFDxIiKx7pf+mG6JZ/ms6jloBSwwcz/Nkn5FMxiq75bQuOH
15+
EV+99S2du/X2bRmD1JxCiMDw8cMacIFBr6BYXsvKOlivwfHBWk8U0f+lVi60jWje
16+
PpKFRd1mYuEZgw==
17+
-----END CERTIFICATE-----

staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ type credentials struct {
178178
// UpdateTransportConfig updates the transport.Config to use credentials
179179
// returned by the plugin.
180180
func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
181+
// If a bearer token is present in the request - avoid the GetCert callback when
182+
// setting up the transport, as that triggers the exec action if the server is
183+
// also configured to allow client certificates for authentication. For requests
184+
// like "kubectl get --token (token) pods" we should assume the intention is to
185+
// use the provided token for authentication.
186+
if c.HasTokenAuth() {
187+
return nil
188+
}
189+
181190
c.Wrap(func(rt http.RoundTripper) http.RoundTripper {
182191
return &roundTripper{a, rt}
183192
})

staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,27 @@ func TestRoundTripper(t *testing.T) {
620620
get(t, http.StatusOK)
621621
}
622622

623+
func TestTokenPresentCancelsExecAction(t *testing.T) {
624+
a, err := newAuthenticator(newCache(), &api.ExecConfig{
625+
Command: "./testdata/test-plugin.sh",
626+
APIVersion: "client.authentication.k8s.io/v1alpha1",
627+
})
628+
if err != nil {
629+
t.Fatal(err)
630+
}
631+
632+
// UpdateTransportConfig returns error on existing TLS certificate callback, unless a bearer token is present in the
633+
// transport config, in which case it takes precedence
634+
cert := func() (*tls.Certificate, error) {
635+
return nil, nil
636+
}
637+
tc := &transport.Config{BearerToken: "token1", TLS: transport.TLSConfig{Insecure: true, GetCert: cert}}
638+
639+
if err := a.UpdateTransportConfig(tc); err != nil {
640+
t.Error("Expected presence of bearer token in config to cancel exec action")
641+
}
642+
}
643+
623644
func TestTLSCredentials(t *testing.T) {
624645
now := time.Now()
625646

test/cmd/authentication.sh

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
set -o nounset
23+
set -o errexit
24+
25+
kube::log::status "Testing kubectl with configured exec credentials plugin"
26+
27+
cat > "${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml << EOF
28+
apiVersion: v1
29+
clusters:
30+
- cluster:
31+
name: test
32+
contexts:
33+
- context:
34+
cluster: test
35+
user: invalid_token_user
36+
name: test
37+
current-context: test
38+
kind: Config
39+
preferences: {}
40+
users:
41+
- name: invalid_token_user
42+
user:
43+
exec:
44+
apiVersion: client.authentication.k8s.io/v1beta1
45+
# Any invalid exec credential plugin will do to demonstrate
46+
command: ls
47+
EOF
48+
49+
### Provided --token should take precedence, thus not triggering the (invalid) exec credential plugin
50+
# Pre-condition: Client certificate authentication enabled on the API server
51+
kube::util::test_client_certificate_authentication_enabled
52+
# Command
53+
output=$(kubectl "${kube_flags_with_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml get namespace kube-system -o name || true)
54+
55+
if [[ "${output}" == "namespace/kube-system" ]]; then
56+
kube::log::status "exec credential plugin not triggered since kubectl was called with provided --token"
57+
else
58+
kube::log::status "Unexpected output when providing --token for authentication - exec credential plugin likely triggered. Output: ${output}"
59+
exit 1
60+
fi
61+
# Post-condition: None
62+
63+
### Without provided --token, the exec credential plugin should be triggered
64+
# Pre-condition: Client certificate authentication enabled on the API server - already checked by positive test above
65+
66+
kube_flags_without_token=('-s' "https://127.0.0.1:${SECURE_API_PORT}" '--insecure-skip-tls-verify=true')
67+
68+
# Command
69+
output2=$(kubectl "${kube_flags_without_token[@]:?}" --kubeconfig="${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml get namespace kube-system -o name 2>&1 || true)
70+
71+
if [[ "${output2}" =~ "json parse error" ]]; then
72+
kube::log::status "exec credential plugin triggered since kubectl was called without provided --token"
73+
else
74+
kube::log::status "Unexpected output when not providing --token for authentication - exec credential plugin not triggered. Output: ${output2}"
75+
exit 1
76+
fi
77+
# Post-condition: None
78+
79+
rm "${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml
80+
81+
set +o nounset
82+
set +o errexit
83+
}

test/cmd/legacy-script.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/../..
2929
# source "${KUBE_ROOT}/hack/lib/test.sh"
3030
source "${KUBE_ROOT}/test/cmd/apply.sh"
3131
source "${KUBE_ROOT}/test/cmd/apps.sh"
32+
source "${KUBE_ROOT}/test/cmd/authentication.sh"
3233
source "${KUBE_ROOT}/test/cmd/authorization.sh"
3334
source "${KUBE_ROOT}/test/cmd/batch.sh"
3435
source "${KUBE_ROOT}/test/cmd/certificate.sh"
@@ -760,6 +761,11 @@ runTests() {
760761
record_command run_nodes_tests
761762
fi
762763

764+
########################
765+
# Authentication
766+
########################
767+
768+
record_command run_exec_credentials_tests
763769

764770
########################
765771
# authorization.k8s.io #

0 commit comments

Comments
 (0)