Check whether static cert is already configured in UpdateTransportConfig

- Also update test-cmd.sh to pass a signing ca to the kube controller
  manager, so CSRs work properly in integration tests.

Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
Margo Crawford 2021-12-07 15:58:46 -08:00
parent 0153febd9f
commit f015fd66ce
10 changed files with 169 additions and 30 deletions

View File

@ -80,7 +80,7 @@ function run_kube_apiserver() {
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
--cert-dir="${TMPDIR:-/tmp/}" \
--service-cluster-ip-range="10.0.0.0/24" \
--client-ca-file=hack/testdata/ca.crt \
--client-ca-file=hack/testdata/ca/ca.crt \
--token-auth-file=hack/testdata/auth-tokens.csv 1>&2 &
export APISERVER_PID=$!
@ -121,6 +121,8 @@ EOF
kube::log::status "Starting controller-manager"
"${KUBE_OUTPUT_HOSTBIN}/kube-controller-manager" \
--kube-api-content-type="${KUBE_TEST_API_TYPE-}" \
--cluster-signing-cert-file=hack/testdata/ca/ca.crt \
--cluster-signing-key-file=hack/testdata/ca/ca.key \
--kubeconfig="${config}" 1>&2 &
export CTLRMGR_PID=$!

19
hack/testdata/auth/testuser.csr vendored Normal file
View File

@ -0,0 +1,19 @@
# this is a test-only certificate request that is used in integration
# tests to test certificate based auth.
# generated with 'openssl req -out testuser.csr -key testuser.key -new -sha256'
# then skipping all the options except for setting CN to testuser
-----BEGIN CERTIFICATE REQUEST-----
MIICWDCCAUACAQAwEzERMA8GA1UEAwwIdGVzdHVzZXIwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC0MWVzF3QC92dQSzA/tBla3okdNkSNyd+SbnzFNxIG
mnm/5vSg1T2b8yx6s0IXLzUK2laY9cB12nS96m3YV+71YFVnXhGpgcxUlQgcr/yk
mcMl545HYBRs5d2m/v7cfYjqtmuvFwEeQeVyI3DpWLEu3DFCas1TpABnDggOcLDe
1YEjBgo5hNlojnKuOqKzJfIWjjbh/wevvxRMZ+5fdi4ilaSh3J13kmvmvrhD2nn6
WqUdjhSsC8oOTmnzbm1RNbpgO6w7SCbTp/Exb3YIvq1T6mzio/N0N4VQgS2uQ+66
ERiVB1iGH+J5jSphcm8sYAtwXQyd+A3d61wo08BaezBVAgMBAAGgADANBgkqhkiG
9w0BAQsFAAOCAQEACQia6OUAsEw0zC6K7fxdI1jnPYqcx8QGqbFQ0yWWKwAlW7rY
PitodCU+0d/88a9ig32Yj01bfOwgU1GPL5NXJmXNtNw1Lu5nQzgiZWzFLjZe3+Ni
bUr8+HhaxHMMV2//SPILtr0aYZIkHPSK3+rcGmo6Mvlr8ANP0G9pwZzb8q2NO3mQ
iXGpFyLq00BEZZyZ3mCGyYGNBpO/pSIxL5eK0gJgT5UJWHZxepi2gsyz+prLjwGo
d5M2z9W6l3/ACHltjEz9t9b1gTZ/fUlNUWRk2frE/NwJDKzL9lhh5qaIKtSsROQ1
OQHcijbY6cSTmgqyAqrGoXMyfJJJvJ4gvludcQ==
-----END CERTIFICATE REQUEST-----

30
hack/testdata/auth/testuser.key vendored Normal file
View File

@ -0,0 +1,30 @@
# this is a test-only private key that is used in integration
# tests to test certificate based auth.
# generated with 'openssl genrsa 2048 > testuser.key'
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAtDFlcxd0AvdnUEswP7QZWt6JHTZEjcnfkm58xTcSBpp5v+b0
oNU9m/MserNCFy81CtpWmPXAddp0vept2Ffu9WBVZ14RqYHMVJUIHK/8pJnDJeeO
R2AUbOXdpv7+3H2I6rZrrxcBHkHlciNw6VixLtwxQmrNU6QAZw4IDnCw3tWBIwYK
OYTZaI5yrjqisyXyFo424f8Hr78UTGfuX3YuIpWkodydd5Jr5r64Q9p5+lqlHY4U
rAvKDk5p825tUTW6YDusO0gm06fxMW92CL6tU+ps4qPzdDeFUIEtrkPuuhEYlQdY
hh/ieY0qYXJvLGALcF0MnfgN3etcKNPAWnswVQIDAQABAoIBABRHBsKX3g9DZhXs
ECbrDntiVY27AKXwFL+2vUW4N8gUdCT7SYYb2Q4GpHe+Wm6felmZTmtyJFFe3wpM
KkZRRuvIBnRWaqhnmeXey4Xm7Ahd4U8PqrwvfLzwdSwIwAdx/Qlks+OFNDQCeF6K
zHcF6xJ55vUxbZ++8eim9JMjbVP2BFcm7a1JjohhstLuWDXo40wk9jozUQu6Tlea
E2YZ52NrUcB9oZq6Q0iW5GXSkGsq/Cc2eSif3TVP9oEnRYL/8vOInuz3sXmHoRTQ
Iz6WHjtq/1OW9OHmqb6MeVL9kUoJx2LLIm6WdZEtSRoDCfi/c3AqK60WnJ/8x3OY
ZT/62AECgYEA5pSsXyD5h89f0PjbnJJWj7Z3eUrw9qMfUGc+FW2LkOFTfDx3M0EM
mdKMFssvn1HUlt+O0KUUE2vGBt/3zlHAfh9XyI01KmPkumarPq+keBCA0KDa3X8i
T2LqTTzBA80CeC3k/+/o7DKXbQCqqDbanNpDTPJUK6WvsRyTA80GhlUCgYEAyA60
lavRjA4nMjoRnI+AuFIgK1flKt+XMF95O8N2I5QgNCKhxoi5nD706dsB5zLWH/A4
hwSJeW6HSFgG7g/lN7CA8dJuQ096FvIoJlBpbUUJOXJWaoU46CGmSw/nx9iTJEm/
9vyVcT0+5xdlBcKec+kcOAHsgsakTycORVbIAgECgYBs44bnQaY1OXwxLmRfc0gH
hA61q+tRPcVa7faGnf3LxGztfYx5Gt39gk1/siJiJX20ZgyGgX6SkVGqC3h16Wty
5BBLeEvEIfpxAG0cJEwSGxEJ9PyGnzQvszGcmfU4e0cQQ/Qh3UTszhNueWMeHxFs
AzLHEqUnG451oF3Y7KiJJQKBgFspmFC7wvk3WdUhcPUOz3Euu0oE+4DV8mcwmmYJ
Y8RCs4oOXfnCRBJW/fOywONB3yEeriOYhay4GR12P3Ir/B+sZpXLdkNl7BSYtIMB
EYmhc9qhp49StePRsZ1y2gPPRP28PaWm49J+PjPADWDalqnrB9jdx5CwRB9thKtE
KFABAoGAcvbPiKOb0/CjkdRE3ysoZlHi9Z2EN9NLiV6EjFSR6H62SsSLBjNvd2Hh
Ii9imj/5jD2qgFXX80+sBoTBqz0WEUt9opxQ5JJ4jXnDWCxZg4sLlLmw+EnjanEl
KMcyXsWn5YOWaY6/e2D8MqNd7vWFZxokFTuhRcJKIHcovKBLJcQ=
-----END RSA PRIVATE KEY-----

17
hack/testdata/ca.crt vendored
View File

@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICpjCCAY4CCQCZBiNB23olFzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAkx
MjcuMC4wLjEwIBcNMjAwNjE0MTk0OTM4WhgPMjI5NDAzMzAxOTQ5MzhaMBQxEjAQ
BgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMxIjMd58IhiiyK4VjmuCWBUZksSs1CcQuo5HSpOqogVZ+vR5mdJDZ56Pw/NSM5c
RqOB3cvjGrxYQe/lKvo9D3UmWLcRKtxdlWxCfPekioJ25/dhGOxtBQcjtp/TSqTM
txprwT4fvsVwiwaURFoCOivF4xjQFG0K1i3/m7CiMHODy67M1EfJDrM7Vv5XPIuJ
VF8HhWBH2HiM25ak34XhxVTX8K97k6wO9OZ5GMqbYuVobTZrSRdiv8s95rkmik6P
jn0ePKqSz6cXNXgXqTl11WtsuoGgjOdB8j/noqTF3m3z17sSBqqG/xBFuSFoNceA
yBDb9ohbs8oY3NIZzyMrt8MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFgcaqRgv
qylx4ogL5iUr0K2e/8YzsvH7zLHG6xnr7HxpR/p0lQt3dPlppECZMGDKElbCgU8f
xVDdZ3FOxHTJ51Vnq/U5xJo+UOMJ4sS8fEH8cfNliSsvmSKzjxpPKqbCJ7VTnkW8
lonedCPRksnhlD1U8CF21rEjKsXcLoX5PsxlS4DX3PtO0+e8aUh9F4XyZagpejq8
0ttXkWd3IyYrpFRGDlFDxIiKx7pf+mG6JZ/ms6jloBSwwcz/Nkn5FMxiq75bQuOH
EV+99S2du/X2bRmD1JxCiMDw8cMacIFBr6BYXsvKOlivwfHBWk8U0f+lVi60jWje
PpKFRd1mYuEZgw==
-----END CERTIFICATE-----

21
hack/testdata/ca/ca.crt vendored Normal file
View File

@ -0,0 +1,21 @@
# this is a test-only public key that is used in integration
# tests as the kube controller manager's signing certificate
# and the api server's client ca file.
# generated with 'openssl req -new -x509 -nodes -days 365000 -key ca.key -out ca.crt', set the CN to 127.0.0.1
-----BEGIN CERTIFICATE-----
MIICpjCCAY4CCQCP7S9OEzUZmDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAkx
MjcuMC4wLjEwIBcNMjIwMTA0MjMzNDMyWhgPMzAyMTA1MDcyMzM0MzJaMBQxEjAQ
BgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMI22ieHLxXpWc58JyCdYG9QmahHfdtDN6R9EwI674gmm81iUq0+bQBoyh4oASos
Xuc3i6Fc7S+rvEeReaYCYQOXk/KotajY3sSCNAGI9oHsdJmrtuLTcPX9vIgcqBU5
BiFaSQEkFtlSGKECI5l/mH+5uMsBkmx7YKj6LyvM/YAPv1WsWrM2IZVPj2eWMljh
uhaDOiQOhrSjmWZuOcRKPTui2IVqTjszuZ7A1r+sKHFSNgTFqEcVSCH8Jcja/35H
LvTpOLdfntVSvklCO20sDGXnNkxcLYz2i2Jm7ixksTeGFPbzfT9q7q3Vyi5FfVvQ
31cGieUwifk3toheQ63WdZMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXF6NCEdP
LMqgmtrfP5bl4F2QLRkVhmqoS88gsQHEUSb1ljz0Jwov4V4/m56Of+jHpQeFbsO7
dXfR4kN3J7cW3UZ8HddIiQvFHW8NPEQvmvVpWfEnNl8aXS9NilowMVDOGwvhQFFS
xsz+1OWXxrF+RpgYB+b0orjy7K1jYER+baXKeLU8JGPKXQGSR+0YomeC/xc2LznD
KfS8t/7D9jbKhFGvTC5x15MSy8rGcdCsFVxtkCmbuq1KwP5CYAyowZ/zCKPtBoKK
5F9TmayjX3AUvh4KVdeh8zrMhFdWmOihKlrj/bJyY0l/hbnROG+ipMPdcepCmfFA
r+6CBEIWxsxJTQ==
-----END CERTIFICATE-----

30
hack/testdata/ca/ca.key vendored Normal file
View File

@ -0,0 +1,30 @@
# this is a test-only private key that is used in integration
# tests as the kube controller manager's signing key.
# generated with 'openssl genrsa 2048 > ca.key'
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwjbaJ4cvFelZznwnIJ1gb1CZqEd920M3pH0TAjrviCabzWJS
rT5tAGjKHigBKixe5zeLoVztL6u8R5F5pgJhA5eT8qi1qNjexII0AYj2gex0mau2
4tNw9f28iByoFTkGIVpJASQW2VIYoQIjmX+Yf7m4ywGSbHtgqPovK8z9gA+/Vaxa
szYhlU+PZ5YyWOG6FoM6JA6GtKOZZm45xEo9O6LYhWpOOzO5nsDWv6wocVI2BMWo
RxVIIfwlyNr/fkcu9Ok4t1+e1VK+SUI7bSwMZec2TFwtjPaLYmbuLGSxN4YU9vN9
P2rurdXKLkV9W9DfVwaJ5TCJ+Te2iF5DrdZ1kwIDAQABAoIBAEpzQo+yl887hAzO
tSUgj048xJqoxUtfah4pQDczpo0317QVBAKbkqd8hDapOUEUf1D9jw5NlDMZD3UU
BGEqOkxpN/Lmz7SpZkRIcW1BNtY5cb1TASnUylHScgpvidOQ55Ozi5bfYc7vVLes
HT+DlU8pP/i6YoophbVhKuIMlAUZ2NkyoFdW+fFUj4r695JIrKIyct+uH+igem6G
OqiTMkAYPwaKHai/yl7UdLRek6xXn1IafTBfvYAL0mdQlOMgTpt/u1oa4EPoxytX
RbsulC6D2cmNlqvc0sQurbwfPeO+eCCwLeFs9aOPy7eWsujoj+lMFdB7oYQdqAEi
VK/p4JECgYEA8JSQMucMr81BZjNldCyLOKb1SHQzyyhrQxnFVEvbVHHCN18VDKf9
p4kjnR8GOqLx/j+gSIUS7nHG+KCLCiwFoK/y+xcf++6yyrv6L8qZq48F+RqoXgGU
H0rxAmdnxRuRVo7Qd2bXbcrvoqS7XVV4YQ1o9pYTEfiGzyxTSRFkyrkCgYEAzqmD
/rEimA/Jbn1MKH3f7U6W49uj6lJeRfxGfbBLqhPCVXOPgnSONjYchYs3TBAwbknm
yPBk6HNUhqnyrZ5Co3n57lSBRpKb4oNh7+n6rZQLT1aTX8Rpf26UVqxjzyuNlTe9
9DYcT9uJm3QI4cdl567KbhmdIJ3zELjri4dlbKsCgYEAhHBkiYEkPMtzyz1UBoZX
BhLzSLWfpaFUmxXeya8QyQXu8uiG//ai7s3SKzbfCPyW125TYFjRuS4taMWIhUsC
thjmXE+4rcoT5wNgkVqpCgasNRv1v/qCfk+VKcufFqU8dzgbjv+wbKKhFRo2gwUA
Rf1J4SVwzh3QqNAv8TOAFxECgYAimhVkponxRetnGYmP2guNMzRmcFcQm7v3Idc3
ojX5AxftsMydfiB1iTfbUETw8mD5hlwi1sU+eljzty7PzQjBUGH9ep6uj0uUcIUM
Y3O6bmg6X3eLeFploMErhe+sN1GcY1nlss2ovUxb/+Cg1P8F/KkeKCG73VZePWgK
05+ywwKBgQDbVh4uufob8ia/1xQxTgao90YeerihmwkdpUEIIU5a22GBOeIUr4jF
ciT4Nm8Rb3noFVzLj36N1LfzrZzmsLmdXzYLx3q2eXlGi3zay0n6mmS7yaRjGmQX
XNc/BzOWHfjPQakxzTsJJaCAPTlQ7+JXNlrLblYrSI5EjBWD1OUG6A==
-----END RSA PRIVATE KEY-----

View File

@ -290,8 +290,8 @@ func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
// also configured to allow client certificates for authentication. For requests
// like "kubectl get --token (token) pods" we should assume the intention is to
// use the provided token for authentication. The same can be said for when the
// user specifies basic auth.
if c.HasTokenAuth() || c.HasBasicAuth() {
// user specifies basic auth or cert auth.
if c.HasTokenAuth() || c.HasBasicAuth() || c.HasCertAuth() {
return nil
}
@ -299,7 +299,7 @@ func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
return &roundTripper{a, rt}
})
if c.TLS.GetCert != nil {
if c.HasCertCallback() {
return errors.New("can't add TLS certificate callback: transport.Config.TLS.GetCert already set")
}
c.TLS.GetCert = a.cert

View File

@ -1206,6 +1206,13 @@ func TestAuthorizationHeaderPresentCancelsExecAction(t *testing.T) {
config.Password = "zelda"
},
},
{
name: "cert auth",
setTransportConfig: func(config *transport.Config) {
config.TLS.CertData = []byte("some-cert-data")
config.TLS.KeyData = []byte("some-key-data")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

View File

@ -82,7 +82,7 @@ EOF
fi
# Post-condition: None
cat > "${TMPDIR:-/tmp}"/valid_exec_plugin.yaml << EOF
cat >"${TMPDIR:-/tmp}"/valid_exec_plugin.yaml <<EOF
apiVersion: v1
clusters:
- cluster:
@ -134,6 +134,54 @@ EOF
fi
# Post-condition: None
### Provided --client-certificate/--client-key should take precedence on the cli, thus not triggering the (invalid) exec credential plugin
# contained in the kubeconfig.
# Use CSR to get a valid certificate
cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: testuser
spec:
request: $(base64 < hack/testdata/auth/testuser.csr | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages: [client auth]
EOF
kube::test::wait_object_assert 'csr/testuser' '{{range.status.conditions}}{{.type}}{{end}}' ''
kubectl certificate approve testuser
kube::test::wait_object_assert 'csr/testuser' '{{range.status.conditions}}{{.type}}{{end}}' 'Approved'
# wait for certificate to not be empty
kube::test::wait_object_assert 'csr/testuser' '{{.status.certificate}}' '.+'
kubectl get csr testuser -o jsonpath='{.status.certificate}' | base64 -d > "${TMPDIR:-/tmp}"/testuser.crt
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)
if [[ "${output5}" =~ "Unauthorized" ]]; then
kube::log::status "Unexpected output when providing --client-certificate/--client-key for authentication - exec credential plugin likely triggered. Output: ${output5}"
exit 1
else
kube::log::status "exec credential plugin not triggered since kubectl was called with provided --client-certificate/--client-key"
fi
### Provided --client-certificate/--client-key should take precedence in the kubeconfig, thus not triggering the (invalid) exec credential plugin.
cat >"${TMPDIR:-/tmp}"/invalid_execcredential.sh <<EOF
#!/bin/bash
echo '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","status":{"clientKeyData":"bad","clientCertificateData":"bad"}}'
EOF
chmod +x "${TMPDIR:-/tmp}"/invalid_execcredential.sh
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
output6=$(kubectl "${kube_flags_without_token[@]:?}" --user testuser get namespace kube-system -o name)
if [[ "${output6}" =~ "Unauthorized" ]]; then
kube::log::status "Unexpected output when kubeconfig was configured with --client-certificate/--client-key for authentication - exec credential plugin likely triggered. Output: ${output6}"
exit 1
else
kube::log::status "exec credential plugin not triggered since kubeconfig was configured with --client-certificate/--client-key for authentication"
fi
kubectl delete csr testuser
rm "${TMPDIR:-/tmp}"/invalid_execcredential.sh
rm "${TMPDIR:-/tmp}"/invalid_exec_plugin.yaml
rm "${TMPDIR:-/tmp}"/valid_exec_plugin.yaml
@ -154,7 +202,7 @@ run_exec_credentials_interactive_tests_version() {
kube::log::status "Testing kubectl with configured ${apiVersion} interactive exec credentials plugin"
cat > "${TMPDIR:-/tmp}"/always_interactive_exec_plugin.yaml << EOF
cat >"${TMPDIR:-/tmp}"/always_interactive_exec_plugin.yaml <<EOF
apiVersion: v1
clusters:
- cluster:
@ -227,7 +275,7 @@ EOF
fi
# Post-condition: None
cat > "${TMPDIR:-/tmp}"/missing_interactive_exec_plugin.yaml << EOF
cat >"${TMPDIR:-/tmp}"/missing_interactive_exec_plugin.yaml <<EOF
apiVersion: v1
clusters:
- cluster:

View File

@ -352,9 +352,7 @@ func execPluginClientTests(t *testing.T, unauthorizedCert, unauthorizedKey []byt
wantMetrics: &execPluginMetrics{},
},
{
// This is not the behavior we would expect, see
// https://github.com/kubernetes/kubernetes/issues/99603
name: "good token with static auth cert and key favors exec plugin",
name: "good token with static auth cert and key favors static cert",
clientConfigFunc: func(c *rest.Config) {
c.ExecProvider.Env = []clientcmdapi.ExecEnvVar{
{
@ -371,9 +369,10 @@ func execPluginClientTests(t *testing.T, unauthorizedCert, unauthorizedKey []byt
c.CertData = unauthorizedCert
c.KeyData = unauthorizedKey
},
wantAuthorizationHeaderValues: [][]string{{"Bearer " + clientAuthorizedToken}},
wantAuthorizationHeaderValues: [][]string{nil},
wantClientErrorPrefix: "Unauthorized",
wantCertificate: x509KeyPair(unauthorizedCert, unauthorizedKey, false),
wantMetrics: &execPluginMetrics{calls: []execPluginCall{{exitCode: 0, callStatus: "no_error"}}},
wantMetrics: &execPluginMetrics{},
},
{
name: "unknown binary",
@ -482,7 +481,7 @@ func TestExecPluginViaClient(t *testing.T) {
_, err = client.CoreV1().ConfigMaps("default").List(ctx, metav1.ListOptions{})
if test.wantClientErrorPrefix != "" {
if err == nil || !strings.HasPrefix(err.Error(), test.wantClientErrorPrefix) {
t.Fatalf(`got %q, wanted "%s..."`, err, test.wantClientErrorPrefix)
t.Fatalf(`got %v, wanted "%s..."`, err, test.wantClientErrorPrefix)
}
} else if err != nil {
t.Fatal(err)