From 02115850aac92c3833c049a32035746091d94644 Mon Sep 17 00:00:00 2001 From: Jeremy Whitlock Date: Fri, 10 Feb 2017 23:34:08 -0700 Subject: [PATCH] apiserver: add pkg/util/webhook tests This commit adds tests for pkg/util/webhooks. The purpose of this was not only for better code coverage but also to alleviate the need for consumers to write their own tests for core functionality. Kubernetes-commit: d15dba7e8bff943d91ba6f58fcb0dfefa357a7f1 --- pkg/util/webhook/BUILD | 18 + pkg/util/webhook/certs_test.go | 214 +++++++++++ pkg/util/webhook/gencerts.sh | 107 ++++++ pkg/util/webhook/webhook_test.go | 607 +++++++++++++++++++++++++++++++ 4 files changed, 946 insertions(+) create mode 100644 pkg/util/webhook/certs_test.go create mode 100755 pkg/util/webhook/gencerts.sh create mode 100644 pkg/util/webhook/webhook_test.go diff --git a/pkg/util/webhook/BUILD b/pkg/util/webhook/BUILD index e14503c07..4a9f48209 100644 --- a/pkg/util/webhook/BUILD +++ b/pkg/util/webhook/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -22,3 +23,20 @@ go_library( "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = [ + "certs_test.go", + "webhook_test.go", + ], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/pkg/api:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library", + ], +) diff --git a/pkg/util/webhook/certs_test.go b/pkg/util/webhook/certs_test.go new file mode 100644 index 000000000..e6b63e2b2 --- /dev/null +++ b/pkg/util/webhook/certs_test.go @@ -0,0 +1,214 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file was generated using openssl by the gencerts.sh script +// and holds raw certificates for the webhook tests. + +package webhook + +var caKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwmdGuDsPPyirvNqlqoDmwf/bmF3zTJBFYQRsJK0vKBAdrfMY +MVwoPEpM5ZZ+VCHEB6vSuGYbC0PyO97H/kIV22FsMwN7zifhJP2T2Hb+B7Nc6s8W +tAQn1J4xUY31xOXOEcXe2nuGezrlKfX3DpA1FVXp6/Cf8vhyUQ0fJXwuZE/Pbhvp +bBUciQLqfPSH6EnShZvzjJBP5Bs13UqzaRobhUf9A3pyk2Mb3PXkTetET7hLc4J2 +uIp5BxOoNZSvgQCvCjRyV5s1his7BNRALKG3qz/48i6JRyO7FvNoxDkWC09zIqD8 +1bw9I1d/+EwWdqAPZLa8iLpSsd0gD03gDSFbOwIDAQABAoIBAQC0JPO5oLDePBf4 +pzxBJbWwLCIXrWfZmQ9RecGksv8xxs1Z9hyDEP0P8WIUlkJ2P9vhp+1ahvOkmtAL +fsQg7qhGZJ7ZHu9I+Fd/6aNpQcrg4+rEhCZrpjYqpnTZOA146eLtQUjjePgDlW3q +Vk0cJ7GpFbXwt0fg5S05wkkMeWib9mKvme3vooNbog52164U1wo/p4WBTpbAMoYA +XlJSqXeoxBpsLWBZHlRG+AYfYpk7BXk8MkIslcKh97RmLsZt52Fh3SbsFJeIEmD5 +2hQDvn/PJojAnM6SMkUqfvv87SdkryvqQYJ80b2D6qd+y8o7gUFr8WkEqVRCqVLh +GaD2C06hAoGBAO9JOe+typoQUPi24aj5BoqWcpyrHQkCdjLlxS805oLsPfmb+EqF +1HwnA8UHNMlrdiczJ8f2M7Y4cSUIEXv6LSE5r4teSiYWASidDLREi0q8scw21CGH +BnCc7PUhUnBngXJ3B1MtCj+r3TFfpOEEi1J1HtMK1AxAaq7zEFzdOrtjAoGBAM/7 +fC89Awvd7yJsgTVumKVx/bA+Q54YJOFMkdba3JbcLsQyn4TBaFv0pwqqXqmkTLZz +WHjkNscomRf9VY34D4q07nO4YGXCBNqm3MaV3mE0xhIyBsATRZnf03O2a/pnRPu/ +yTE1EyuIqK/l4+5iv2O5mWzxorC4qdV34Wf5WCRJAoGBANfmfjvf1zoDFswSVrGb +X2eUL31kdyI18mgiITRiysm+VnztWa4D6qDKowAXbG2AZG8iHPazEh2L96quCPiP +1kBwSA+717Ndj1YRvfC5F+UrNFFJ90T5C7p4HOVgV33MJmQdOaK2tNSWQVHXNnFB +JGQWAOXykzkqthd8gHsJsYB5AoGAd7BfKAQxg5vFqYbN2MT7vYJbHxjF6u40Ex/w +cbfj6EFv/GKxoEF5YCnsE1w2O+QcbYb1rCSRTY2UhNS6bogJ0aYL77Z0azr7diU+ +ul226zPmpMP7VIACtumzE00w2JqjfUlCbDoB/TSY9xkSUbasM6S0oZhxKsgqnHlv +01kQG1kCgYBPZfZiqKwnnsOSRy8UBN4Oo5zg9QrbMki472/s4FhHaunXF0pFmIUG +QU/9kYteJ8DlCppvvtU5C3qmEkW6c2u8KAfJXRmA51uS6v36kEx/8313ZJ5afwLU +i2ZMmS8OabHjIhdnCSA2N7geqaAZa7BCLqt8735Doys1p0KB0y+ZNw== +-----END RSA PRIVATE KEY-----`) + +var caCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDNzCCAh+gAwIBAgIJAM1Z9H27fWCyMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV +BAMUEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwMjExMDAyMjUzWhgPMjI5MDExMjcw +MDIyNTNaMBsxGTAXBgNVBAMUEHdlYmhvb2tfdGVzdHNfY2EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDCZ0a4Ow8/KKu82qWqgObB/9uYXfNMkEVhBGwk +rS8oEB2t8xgxXCg8Skzlln5UIcQHq9K4ZhsLQ/I73sf+QhXbYWwzA3vOJ+Ek/ZPY +dv4Hs1zqzxa0BCfUnjFRjfXE5c4Rxd7ae4Z7OuUp9fcOkDUVVenr8J/y+HJRDR8l +fC5kT89uG+lsFRyJAup89IfoSdKFm/OMkE/kGzXdSrNpGhuFR/0DenKTYxvc9eRN +60RPuEtzgna4inkHE6g1lK+BAK8KNHJXmzWGKzsE1EAsoberP/jyLolHI7sW82jE +ORYLT3MioPzVvD0jV3/4TBZ2oA9ktryIulKx3SAPTeANIVs7AgMBAAGjfDB6MB0G +A1UdDgQWBBS0/gwwXmdxIe8o+a7WKbdiTYPy+TBLBgNVHSMERDBCgBS0/gwwXmdx +Ie8o+a7WKbdiTYPy+aEfpB0wGzEZMBcGA1UEAxQQd2ViaG9va190ZXN0c19jYYIJ +AM1Z9H27fWCyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHGUdTCN +rm0Mx6V5SmSIGbOB/+3QMqE1ZBIWwPsVFKHhROLaouELZFO+QysfLufB8SS54aM6 +ewufjfSz4KL26DnoSwOFirPxpG0+Sdry55lCjmZ50KtENZDu6g288Qx9GBzqgVHz +kGi/eciV4fZ4HYIhZY+oR29n3YYQOID4UqbQ86lSoN781dmsEQLL+TEK4mJJFcNg +SKHM526WdwJ15zqpKNlcqXtTyx3UfBFlNwvrxHNFbth1vOfdTW8zAs9Xzcr5vSm2 +G8nJ3FF/UF4dYpjDzggO3ALZZqUJHnl/XusETo5kYY3Ozp0xQYg2beR8irElqP8f +oNcE4Ycfe10Hmec= +-----END CERTIFICATE-----`) + +var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxrCZsZHIeKuYSUYw4qKB8P+HeU81VP7F0tVbLOffj587sfDu +N2QsOsnmiWKS9HiNVUs5ap/Zg3rekbnZV5eBeS5qOPC95c8Oyac7A2HNYSRp08kQ +dUUo3ueDqwmZ5dNiDp0SriQCWfPYxiWHdWSacqDxxzXPFWCs6z3vwFYV35VxDFhr +M8gPWgSqjWdqj+p5cHrL88me7vWWsj6ceL/i2KWo8lmuHuhUzggn0ekU/aKsavHi +X/MNVd5tPzGIR+B49aywQZ9KyrJ7V8SdFqKYEGfH6RaiDhaNNUOv9E9668PdupnG +qei0260zB8SDY4j6VKPGOS90YBA66qOP6J11rwIDAQABAoIBAQCsgs0PNep/i01v +4Xe0bzCvVM4Fb9Z4c7TDN+gv9ytOggzMlMngYiNc78wwYNwDU2AzPFsfzqaG1/nD +QUAKI0uRMdGcmrnmfH70azR73UD7JSiVb6/QgjnYP988c9uhhoVO9uYvOKip/WSr +tg4EyVKoUEFcm8WvY/7/SQmPT68yLf5VpbtuCysAkSLPUjcBer4A6eWwlZ9PWrtj +rLjUCGXXDKRgMmQRAwL+tpBgMwb1+euriv4+M6ddZCkcyaohW076qA3aaq3+XtAB +RTGQubWshuri3N1WRQcn1ZvGURLCAhI8q+9i/wXADKrAlL6imDuYzTW+LMQdZLuH +bwHvq/yRAoGBAP3beuc2R/jjkDttFsSce2dMx6F8AKPpmdbzVw7/DhflzNRDM/Yo +dfVOabRLqcAyfhNm2L6CdUaIJuHRKyRJT3X5wgxUapAjXFUE0kH+qnaq3BxZCCjU +fwDUZ4SUVDAuyaMo5OfVbqkI/L3rvSSgklNOnSkXMPtftDkz8pVljLo9AoGBAMhd +6uiddCt3Dpt75C1BDRX0xGKc4KwtPK0CnQeQmQNXUx192m6IhfPW7YUoKvIZibWB +f9NNJ/KCxDGG+QP7X+0sWQZMfdp5f1l6EsM6HFPLAOgjQ4PyBVWqxknJyxy6GCnt +vI3s6cwMxN7B7QJ/87ffO23elEu7bCdg0lrOAmpbAoGBALN6fI+B6irGwU+ylflV +5U2olC/Q2ycIXtMBYpjgrRcqSsH77X3pJ1TTJprpL9AKIucWvMEcvUursUnQt97E +0iBH//D1sg3MYlhdu0Ybhmu16z9Dlyg+7LgqdDHhKRCT082+ePCMDtwF1aN1S1nd +CPdLSoQluGTRSjtzRdxoWrHFAoGAJqNlz2G9qzwURwuHHuryeQ9wZ4vVD57RmpNs +cK8Dss8+KevBGZueKT2DJDBwx6sBEU1dtwOj9nIdH2fl0UzCXNw2dq59fon7cufF +gnxMRiRZkmpqdKFRQgninwwY7Ps9+afsunm7RCwaMtK2v8qo1wZnUXKgqlIEMzvK +lNQxRw0CgYEA0uT5TkrZ3S+GAbyQtwb6FtXiLoocL+8QyBw6d1+5Fy7D0pYYQDLw +TMeR2NOEqQGLgsqCnbuKzKBIuIY8wm0VIzOqRGmk4fWiOGxrtEBrfPl+A21bexyC +qv5UBMLcEinZEM3x1e/rloDwKi0IGfyKiRfVpxdVKebs7dJfFYkhmw0= +-----END RSA PRIVATE KEY-----`) + +var badCACert = []byte(`-----BEGIN CERTIFICATE----- +MIIDNzCCAh+gAwIBAgIJAPbb5w6p8Cw8MA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV +BAMUEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwMjExMDAyMjUzWhgPMjI5MDExMjcw +MDIyNTNaMBsxGTAXBgNVBAMUEHdlYmhvb2tfdGVzdHNfY2EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDGsJmxkch4q5hJRjDiooHw/4d5TzVU/sXS1Vss +59+Pnzux8O43ZCw6yeaJYpL0eI1VSzlqn9mDet6RudlXl4F5Lmo48L3lzw7JpzsD +Yc1hJGnTyRB1RSje54OrCZnl02IOnRKuJAJZ89jGJYd1ZJpyoPHHNc8VYKzrPe/A +VhXflXEMWGszyA9aBKqNZ2qP6nlwesvzyZ7u9ZayPpx4v+LYpajyWa4e6FTOCCfR +6RT9oqxq8eJf8w1V3m0/MYhH4Hj1rLBBn0rKsntXxJ0WopgQZ8fpFqIOFo01Q6/0 +T3rrw926mcap6LTbrTMHxINjiPpUo8Y5L3RgEDrqo4/onXWvAgMBAAGjfDB6MB0G +A1UdDgQWBBTTHlbuK0loVSNNa+TCM0Bt7dLEcTBLBgNVHSMERDBCgBTTHlbuK0lo +VSNNa+TCM0Bt7dLEcaEfpB0wGzEZMBcGA1UEAxQQd2ViaG9va190ZXN0c19jYYIJ +APbb5w6p8Cw8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBACrsdqfQ +7be0HI4+/cz/VwzvbQwjr9O6Ybxqs/eKOprh8RWRtjYeuagvTc6f5jH39W9kZdjs +E3ktCG3RJJ/SyooeJlzNhgaAaATnMqEf7GyiQv3ch0B/Mc4TOPJeQ2E/pzFj3snq +Edm8Xu9+WLwTTF4j/WlSY1sgVSnFk3Yzl5cn0ip00DVCOsL2sP3JlX9HRG4IrdiX +jYXb+nGUPYFultSGSUw+5SiL2yM1ZyHfOBaO1RH8QIRA1/aFTfE+1QRuja5YtCwl +ahpWVRhii7GVR3zKEgKFTxjELHm8x3vBC/HAhj5J3433nlRrgvwZXsZYplqp8422 +IpexMtsutA+y9aE= +-----END CERTIFICATE-----`) + +var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA2lF1EXRYzsUIwM1vrMM/OIinPvOzqSQSQ0euiUYgcNTw2Y87 +2fNWnQXOP3JDHgzZw231kJbdRxeWgRdeTnvCFUaBzA63GXMMCljs1sgAVLXnAmWR +sE/TzG/OwsZUnyYzQMdG49PAEpw8GlX9t0eojmXPy7C/M0GnyAMx/UyMrq40lht6 +gdUI2gYRUofRRtHFs+a0EX8/q+49ciwgQx5inj2BX3Jc2cvc35Y3bSY86CYsAZic +PZP84wP5iWkYmvFhJUoS/JY2FMC6CvRFPcTi8Dnp28kHEaqrocmwajSfyiJe/1qJ +dMlHAInvTxp9E53cOzfCP6nmHbSKQPxm5u8hSQIDAQABAoIBAQCDhfNbEpa16qn9 +TUZr9CxQKLNpD3Q6/8oo0jRp6t98WizHRK0v/pM9gdPhETsyDVfbjpEUDG8+dw1q +s+NSsOgZ3SIxBuRz5oVobm4wbskUP4nuPbZpW44jaXBMkyNDxcW2ztb8RgM+svTa +gNea5Qa80sU+1zo47OLhcltZWBag3KCU/JQT+3LThVZDHt3GRx4QCASTJx3v/vBB +o9M5wCYZp6sP7wmFUZfwEpkTfJ5M7sG1h7ibD/8kjIvpnQj+OFpcoylDxTINvqsN +ADAe1NPK00Rx6vE9GNQ8ZA/lg0pih+EpK4PpE5cDDkYs3VchUlYHBSrsc7+K6kUk +mMTdmVvpAoGBAP7sHhKMEpmPIUqxb5M95l+PX5uOS0x08HM40Vr8mxgx4z849CpW +1vcQlZwcXwkxUfJyXZCPx9CK0Sw877Afpac1OL4RiEKZ3qmwLeI9RnRTeKC5qXJ9 +u31l+dgoSbRZDUdcM1ZwFs9+V8+zId58SifDaBjm3466VCMnD7KQUz4jAoGBANs9 +udy4Os+SvCYVUaiVvtoYMdcyym+VQzf3ycVAk91dO8Qha/5uFYD/f7ykgEgg7QCd +jQp+ZVYPD7Hbh8XNwAt/6T+bF1qe8TSM3K8uk2Wt/tlk1ZqRnNNYsIZ8BO8c4T+f +pbu/mCDdmTKWQWVEwCj2kKNBHptmlLO5Ie2nebujAoGBAIqoZccS138dAi+9iYHe +VnM96fQTltN0e+FAU2eZJMcpQ4D8+poY9/4U0DvEltDKOdeU612ZR0cgapwUXQ9A +d3sWkNGZebM4PIux35NCXxMg3+kUc51p1FRl5lrztvtYwMdC2E241D9yalL4DYEV +u8QbHoEE+y6IHQGt2nT22cBfAoGAWmZuT+uLHHIFsLJTtG7ifi1Bx9lCjZX/XIGI +qhQBpGJANZQOYp/jsAgqFI/D8XnaH8nXET+i60RUlWLO7inziQpaFAcQLyagkKmQ +iY9r6Z5AGkWwqgZmouLMDvfuVOYUntZmUS8kPFEDTU+VcXtSvNFGPHqqcytuH1kz ++zl2QX8CgYB9nFMpqARHl0kJp1UXtq9prUei+Lyu2gvrl3a74w96gqg3yx9+fU/n +FzGF2VXnC5n1KvCvQi3xjLNzCOXKM3igu7u50CiaA/HEYmyWyOJw2Nt2+ICvxcCH +rnsA8P8I/R5Esl0rvv2BbA1Q1O6SLC+Dfnhf7KulWmNgqVXKllj+Ng== +-----END RSA PRIVATE KEY-----`) + +var serverCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIJAPJbY53f15/vMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV +BAMUEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwMjExMDAyMjU0WhgPMjI5MDExMjcw +MDIyNTRaMB8xHTAbBgNVBAMMFHdlYmhvb2tfdGVzdHNfc2VydmVyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2lF1EXRYzsUIwM1vrMM/OIinPvOzqSQS +Q0euiUYgcNTw2Y872fNWnQXOP3JDHgzZw231kJbdRxeWgRdeTnvCFUaBzA63GXMM +Cljs1sgAVLXnAmWRsE/TzG/OwsZUnyYzQMdG49PAEpw8GlX9t0eojmXPy7C/M0Gn +yAMx/UyMrq40lht6gdUI2gYRUofRRtHFs+a0EX8/q+49ciwgQx5inj2BX3Jc2cvc +35Y3bSY86CYsAZicPZP84wP5iWkYmvFhJUoS/JY2FMC6CvRFPcTi8Dnp28kHEaqr +ocmwajSfyiJe/1qJdMlHAInvTxp9E53cOzfCP6nmHbSKQPxm5u8hSQIDAQABo0ow +SDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYI +KwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOCAQEAX/CG +3C1nwCWabpBw6h0k7UlDI55nnTH6xHdSX9EFmHz49NmAym9gUwXK5xDPVDNYURjb +TD3R2e76Cov7wXRzw99BMzKOhNrMgjiOrc0WT4Ck5MOaKgjzZEIXRSSBllsrF9ut +hnnuSaaKwUVn4D/9vPMp/TuZoK7yZaW3Pyv0ScQfpkECDLKYIkXOlyhC/I5Tfbof ++zReStbTsc0EWMVLLIAbP7uPf1VcH5HnElh1ignxRAPBsXwF8jQzjUBTWcZ5dEi9 +ofIrWo+AVKvcoRlyZZyLjOKPzhA5+pwG4yBkWJB5Cshq2trOYVf3+uUN8lz6i57M +wqxS1Q1MmtLhyhy79Q== +-----END CERTIFICATE-----`) + +var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAt6auF7X7jl94ilPpJid6k15/Tnt/hFWMRv2eGeuSBT1YMjDP +X5pIaseeIVdXl0ePKzPeyYJma4t7EpGhUO7TX12Cr1e5S8IMExNDpYGti3ncLf1O +HP+faJNAlbKMTgf2xr0HPg8Q2lsDKfam/OEn+7jqv3FpZ5guwQQy7beQhWV38YuR +vf2ChNJVcalN0h+MRNkT+hsGKGM9XKgKFGknDuCP8N0H7HrP7LLf/tLOMNq/PeMz +I6MXMlXmB4VRPMlf1zJGfvE6i0sSbNM0p2ZjArpjhjdveLuvBwan/Guk9vW970ON +sNn9gdLiwCSLqzhRy0cTlIJsSnlkhbuOQZsqKwIDAQABAoIBAE2gCVQCWsrJ9dRa +NWEoLLpfpeXRc4vG8R0MlCgWl0jZrg7A7NZXCyb/KwqitWY/G/fB2/hGwu3QLfwi +TBI+cF+N0fA1Xx/zbFEfwmcRkf4zSuqxd7PwJDv6icD8kCtnWFqWiZokmhYBhCvX +kquuq8zNU4QJ9uiPvateD/zEqzSGgMeL+j7RGJsRmh2TnSBgKXLwadRhYLeHiFu/ +AwoWljlhLNXrCzCLx2kJPIA9CNYYtShhQncfZfkfC0I02vPWX9hu8lMpKQp2MmD9 +b3DvVW3H6cjAtm/nsjGghYNCngep8uPX2twcrLOZfzJgsZJf+yn/KLWb/yhGBXjd +TERHRCECgYEA2i5OfkIrBIPQcjhQCtkjYBgKUKVS54KTTPQQ0zoiGRPkXb6kpqrt +kaCKGYXT4oqvQQapNZQykrLUQ/xzbdAAzdIwZ8hTWS5K5cxHOnkmOcPiKu2+jM4I +zT7sdAYn0aSbrh1pNRQDV0tQZcI1Urp/OcEuniaEblWhq5/VRCmpCBECgYEA13wg +jKRobq4QBoQM8pu1Ha7waeJZ26NcZwCxno0TwH2JZ6X9e4iXfhywUOgVW7hXzcs5 +2nBciVX5h31u1EDPJz6aPyzzQHi0YspDy/zuO0GWEJxLKm5QMyjh5vakhF5bVP6f +Dh3rXts/ZYKk3+p4ezXs2b+uTelowuq8Kk55qnsCgYAy/tvN2v1fAsg3yj27K2F/ +Vl8i1mF4RybSt8Eu/cl2fxXDa4nkgtMgVJuyt3r82ll4I2xtX4Qqka3Xbiw0oIdv +lA9IUqRYld9fss17N1Hd8pDsY8FD++xGvMxbmgy4jXbtzWYHx/O39ZyHDEuWWIzg +HO0effY6K72r9aHNWsdtYQKBgQDNXUw8Hbg1u4gkXZdlZEYxevc/Qmz3KXK36+5b +uAJaEopwkL7LC/utQjQ7d2RbnI154TRK3Ykjjh+ZJE8K1JVYxo4EpZdTG3Z3LGN+ +tphpOvGE9R+h2a5vg4gAMZHLYY3TrDL0JkmahoOd/+uYR4L5kgQf5lF9iXTBRyt7 +enzznwKBgBr9rHUsdd8whEo/UntKaGQ6sqevd/ESxDErUqkTkiTtDAT2vuLQwgA0 +JnzYUv1/wmoLCz6Nbe6Fg9shAgRY/tz7GI75NBi4HBjp286df6Uu6qEJ4LAELc0+ +Yh/uRF/H4WkpmBckEXobnRxoX/0HWFY5SETLoG/nwaxJG9YL/Acr +-----END RSA PRIVATE KEY-----`) + +var clientCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIJAPJbY53f15/wMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV +BAMUEHdlYmhvb2tfdGVzdHNfY2EwIBcNMTcwMjExMDAyMjU0WhgPMjI5MDExMjcw +MDIyNTRaMB8xHTAbBgNVBAMMFHdlYmhvb2tfdGVzdHNfY2xpZW50MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt6auF7X7jl94ilPpJid6k15/Tnt/hFWM +Rv2eGeuSBT1YMjDPX5pIaseeIVdXl0ePKzPeyYJma4t7EpGhUO7TX12Cr1e5S8IM +ExNDpYGti3ncLf1OHP+faJNAlbKMTgf2xr0HPg8Q2lsDKfam/OEn+7jqv3FpZ5gu +wQQy7beQhWV38YuRvf2ChNJVcalN0h+MRNkT+hsGKGM9XKgKFGknDuCP8N0H7HrP +7LLf/tLOMNq/PeMzI6MXMlXmB4VRPMlf1zJGfvE6i0sSbNM0p2ZjArpjhjdveLuv +Bwan/Guk9vW970ONsNn9gdLiwCSLqzhRy0cTlIJsSnlkhbuOQZsqKwIDAQABo0ow +SDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYI +KwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOCAQEAD8aZ +E0nof9Rh7s+uwtF70KPgcz71ft0c1+vSmeLm4IkN0f+amcvgaT8xZLwNv1b77NZo +uMWXvit24eIuiqzq7umKiHP/UrFv+Rl+9ue+lA3N0e3WikRoJsh3aoIn8BQUBbnX +Nr9R69SeRYYRpMrs19N5Wn4gN7Nfie+1FKWsL3myJYDFsg+8GMEcOJ0YdOMALMy0 +tIJdYji28mTQ++lpGbekjhf7p9wazQ/6CVd8WNpIbGO84QbGCcpCaVM2XxOSiV/F +hIGO1Z30SBq8rQw51XbhdRX+uvRM1ya4RuBMCSX/hpsMu9lVRqCzbkU4PvuUTqLA +CebKCgjYbM0CWrP9kw== +-----END CERTIFICATE-----`) diff --git a/pkg/util/webhook/gencerts.sh b/pkg/util/webhook/gencerts.sh new file mode 100755 index 000000000..fdceb9571 --- /dev/null +++ b/pkg/util/webhook/gencerts.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# gencerts.sh generates the certificates for the webhook tests. +# +# It is not expected to be run often (there is no go generate rule), and mainly +# exists for documentation purposes. + +CN_BASE="webhook_tests" + +cat > server.conf << EOF +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +[req_distinguished_name] +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +[alt_names] +IP.1 = 127.0.0.1 +EOF + +cat > client.conf << EOF +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name +[req_distinguished_name] +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth, serverAuth +subjectAltName = @alt_names +[alt_names] +IP.1 = 127.0.0.1 +EOF + +# Create a certificate authority +openssl genrsa -out caKey.pem 2048 +openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=${CN_BASE}_ca" + +# Create a second certificate authority +openssl genrsa -out badCAKey.pem 2048 +openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=${CN_BASE}_ca" + +# Create a server certiticate +openssl genrsa -out serverKey.pem 2048 +openssl req -new -key serverKey.pem -out server.csr -subj "/CN=${CN_BASE}_server" -config server.conf +openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf + +# Create a client certiticate +openssl genrsa -out clientKey.pem 2048 +openssl req -new -key clientKey.pem -out client.csr -subj "/CN=${CN_BASE}_client" -config client.conf +openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf + +outfile=certs_test.go + +cat > $outfile << EOF +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +EOF + +echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile +echo "// and holds raw certificates for the webhook tests." >> $outfile +echo "" >> $outfile +echo "package webhook" >> $outfile +for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do + data=$(cat ${file}.pem) + echo "" >> $outfile + echo "var $file = []byte(\`$data\`)" >> $outfile +done + +# Clean up after we're done. +rm *.pem +rm *.csr +rm *.srl +rm *.conf diff --git a/pkg/util/webhook/webhook_test.go b/pkg/util/webhook/webhook_test.go new file mode 100644 index 000000000..232389c06 --- /dev/null +++ b/pkg/util/webhook/webhook_test.go @@ -0,0 +1,607 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/pkg/api" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api/v1" +) + +const ( + errBadCertificate = "Get .*: remote error: tls: bad certificate" + errNoConfiguration = "invalid configuration: no configuration has been provided" + errMissingCertPath = "invalid configuration: unable to read %s %s for %s due to open %s: .*" + errSignedByUnknownCA = "Get .*: x509: certificate signed by unknown authority" +) + +var ( + defaultCluster = v1.NamedCluster{ + Cluster: v1.Cluster{ + Server: "https://webhook.example.com", + CertificateAuthorityData: caCert, + }, + } + defaultUser = v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificateData: clientCert, + ClientKeyData: clientKey, + }, + } + namedCluster = v1.NamedCluster{ + Cluster: v1.Cluster{ + Server: "https://webhook.example.com", + CertificateAuthorityData: caCert, + }, + Name: "test-cluster", + } + groupVersions = []schema.GroupVersion{} + retryBackoff = time.Duration(500) * time.Millisecond +) + +// TestDisabledGroupVersion ensures that requiring a group version works as expected +func TestDisabledGroupVersion(t *testing.T) { + gv := schema.GroupVersion{Group: "webhook.util.k8s.io", Version: "v1"} + gvs := []schema.GroupVersion{gv} + _, err := NewGenericWebhook(api.Registry, api.Codecs, "/some/path", gvs, retryBackoff) + + if err == nil { + t.Errorf("expected an error") + } else { + aErrMsg := err.Error() + eErrMsg := fmt.Sprintf("webhook plugin requires enabling extension resource: %s", gv) + + if aErrMsg != eErrMsg { + t.Errorf("unexpected error message mismatch:\n Expected: %s\n Actual: %s", eErrMsg, aErrMsg) + } + } +} + +// TestKubeConfigFile ensures that a kube config file, regardless of validity, is handled properly +func TestKubeConfigFile(t *testing.T) { + badCAPath := "/tmp/missing/ca.pem" + badClientCertPath := "/tmp/missing/client.pem" + badClientKeyPath := "/tmp/missing/client-key.pem" + dir := bootstrapTestDir(t) + + defer os.RemoveAll(dir) + + // These tests check for all of the ways in which a Kubernetes config file could be malformed within the context of + // configuring a webhook. Configuration issues that arise while using the webhook are tested elsewhere. + tests := []struct { + test string + cluster *v1.NamedCluster + context *v1.NamedContext + currentContext string + user *v1.NamedAuthInfo + errRegex string + }{ + { + test: "missing context (no default, none specified)", + cluster: &namedCluster, + errRegex: errNoConfiguration, + }, + { + test: "missing context (specified context is missing)", + cluster: &namedCluster, + currentContext: "missing-context", + errRegex: errNoConfiguration, + }, + { + test: "context without cluster", + context: &v1.NamedContext{ + Context: v1.Context{}, + }, + currentContext: "testing-context", + errRegex: errNoConfiguration, + }, + { + test: "context without user", + cluster: &namedCluster, + context: &v1.NamedContext{ + Context: v1.Context{ + Cluster: namedCluster.Name, + }, + }, + currentContext: "testing-context", + errRegex: "", // Not an error at parse time, only when using the webhook + }, + { + test: "context with missing cluster", + cluster: &namedCluster, + context: &v1.NamedContext{ + Context: v1.Context{ + Cluster: "missing-cluster", + }, + }, + errRegex: errNoConfiguration, + }, + { + test: "context with missing user", + cluster: &namedCluster, + context: &v1.NamedContext{ + Context: v1.Context{ + Cluster: namedCluster.Name, + AuthInfo: "missing-user", + }, + }, + currentContext: "testing-context", + errRegex: "", // Not an error at parse time, only when using the webhook + }, + { + test: "cluster with invalid CA certificate path", + cluster: &v1.NamedCluster{ + Cluster: v1.Cluster{ + Server: namedCluster.Cluster.Server, + CertificateAuthority: badCAPath, + }, + }, + user: &defaultUser, + errRegex: fmt.Sprintf(errMissingCertPath, "certificate-authority", badCAPath, "", badCAPath), + }, + { + test: "cluster with invalid CA certificate ", + cluster: &v1.NamedCluster{ + Cluster: v1.Cluster{ + Server: namedCluster.Cluster.Server, + CertificateAuthorityData: caKey, + }, + }, + user: &defaultUser, + errRegex: "", // Not an error at parse time, only when using the webhook + }, + { + test: "user with invalid client certificate path", + cluster: &defaultCluster, + user: &v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificate: badClientCertPath, + ClientKeyData: defaultUser.AuthInfo.ClientKeyData, + }, + }, + errRegex: fmt.Sprintf(errMissingCertPath, "client-cert", badClientCertPath, "", badClientCertPath), + }, + { + test: "user with invalid client certificate", + cluster: &defaultCluster, + user: &v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificateData: clientKey, + ClientKeyData: defaultUser.AuthInfo.ClientKeyData, + }, + }, + errRegex: "tls: failed to find certificate PEM data in certificate input, but did find a private key; PEM inputs may have been switched", + }, + { + test: "user with invalid client certificate path", + cluster: &defaultCluster, + user: &v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificateData: defaultUser.AuthInfo.ClientCertificateData, + ClientKey: badClientKeyPath, + }, + }, + errRegex: fmt.Sprintf(errMissingCertPath, "client-key", badClientKeyPath, "", badClientKeyPath), + }, + { + test: "user with invalid client certificate", + cluster: &defaultCluster, + user: &v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificateData: defaultUser.AuthInfo.ClientCertificateData, + ClientKeyData: clientCert, + }, + }, + errRegex: "tls: found a certificate rather than a key in the PEM for the private key", + }, + { + test: "valid configuration (certificate data embeded in config)", + cluster: &defaultCluster, + user: &defaultUser, + errRegex: "", + }, + { + test: "valid configuration (certificate files referenced in config)", + cluster: &v1.NamedCluster{ + Cluster: v1.Cluster{ + Server: "https://webhook.example.com", + CertificateAuthority: filepath.Join(dir, "ca.pem"), + }, + }, + user: &v1.NamedAuthInfo{ + AuthInfo: v1.AuthInfo{ + ClientCertificate: filepath.Join(dir, "client.pem"), + ClientKey: filepath.Join(dir, "client-key.pem"), + }, + }, + errRegex: "", + }, + } + + for _, tt := range tests { + // Use a closure so defer statements trigger between loop iterations. + err := func() error { + kubeConfig := v1.Config{} + + if tt.cluster != nil { + kubeConfig.Clusters = []v1.NamedCluster{*tt.cluster} + } + + if tt.context != nil { + kubeConfig.Contexts = []v1.NamedContext{*tt.context} + } + + if tt.user != nil { + kubeConfig.AuthInfos = []v1.NamedAuthInfo{*tt.user} + } + + kubeConfigFile, err := newKubeConfigFile(kubeConfig) + + if err == nil { + defer os.Remove(kubeConfigFile) + + _, err = NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, retryBackoff) + } + + return err + }() + + if err == nil { + if tt.errRegex != "" { + t.Errorf("%s: expected an error", tt.test) + } + } else { + if tt.errRegex == "" { + t.Errorf("%s: unexpected error: %v", tt.test, err) + } else if !regexp.MustCompile(tt.errRegex).MatchString(err.Error()) { + t.Errorf("%s: unexpected error message to match:\n Expected: %s\n Actual: %s", tt.test, tt.errRegex, err.Error()) + } + } + } +} + +// TestMissingKubeConfigFile ensures that a kube config path to a missing file is handled properly +func TestMissingKubeConfigFile(t *testing.T) { + kubeConfigPath := "/some/missing/path" + _, err := NewGenericWebhook(api.Registry, api.Codecs, kubeConfigPath, groupVersions, retryBackoff) + + if err == nil { + t.Errorf("creating the webhook should had failed") + } else if strings.Index(err.Error(), fmt.Sprintf("stat %s", kubeConfigPath)) != 0 { + t.Errorf("unexpected error: %v", err) + } +} + +// TestTLSConfig ensures that the TLS-based communication between client and server works as expected +func TestTLSConfig(t *testing.T) { + invalidCert := []byte("invalid") + tests := []struct { + test string + clientCert, clientKey, clientCA []byte + serverCert, serverKey, serverCA []byte + errRegex string + }{ + { + test: "invalid server CA", + clientCert: clientCert, clientKey: clientKey, clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, serverCA: invalidCert, + errRegex: errBadCertificate, + }, + { + test: "invalid client certificate", + clientCert: invalidCert, clientKey: clientKey, clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, serverCA: caCert, + errRegex: "tls: failed to find any PEM data in certificate input", + }, + { + test: "invalid client key", + clientCert: clientCert, clientKey: invalidCert, clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, serverCA: caCert, + errRegex: "tls: failed to find any PEM data in key input", + }, + { + test: "client does not trust server", + clientCert: clientCert, clientKey: clientKey, + serverCert: serverCert, serverKey: serverKey, + errRegex: errSignedByUnknownCA, + }, + { + test: "server does not trust client", + clientCert: clientCert, clientKey: clientKey, clientCA: badCACert, + serverCert: serverCert, serverKey: serverKey, serverCA: caCert, + errRegex: errSignedByUnknownCA + " .*", + }, + { + test: "server requires auth, client provides it", + clientCert: clientCert, clientKey: clientKey, clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, serverCA: caCert, + errRegex: "", + }, + { + test: "server does not require client auth", + clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, + errRegex: "", + }, + { + test: "server does not require client auth, client provides it", + clientCert: clientCert, clientKey: clientKey, clientCA: caCert, + serverCert: serverCert, serverKey: serverKey, + errRegex: "", + }, + { + test: "webhook does not support insecure servers", + errRegex: errSignedByUnknownCA, + }, + } + + for _, tt := range tests { + // Use a closure so defer statements trigger between loop iterations. + func() { + // Create and start a simple HTTPS server + server, err := newTestServer(tt.serverCert, tt.serverKey, tt.serverCA, nil) + + if err != nil { + t.Errorf("%s: failed to create server: %v", tt.test, err) + return + } + + defer server.Close() + + // Create a Kubernetes client configuration file + configFile, err := newKubeConfigFile(v1.Config{ + Clusters: []v1.NamedCluster{ + { + Cluster: v1.Cluster{ + Server: server.URL, + CertificateAuthorityData: tt.clientCA, + }, + }, + }, + AuthInfos: []v1.NamedAuthInfo{ + { + AuthInfo: v1.AuthInfo{ + ClientCertificateData: tt.clientCert, + ClientKeyData: tt.clientKey, + }, + }, + }, + }) + + if err != nil { + t.Errorf("%s: %v", tt.test, err) + return + } + + defer os.Remove(configFile) + + wh, err := NewGenericWebhook(api.Registry, api.Codecs, configFile, groupVersions, retryBackoff) + + if err == nil { + err = wh.RestClient.Get().Do().Error() + } + + if err == nil { + if tt.errRegex != "" { + t.Errorf("%s: expected an error", tt.test) + } + } else { + if tt.errRegex == "" { + t.Errorf("%s: unexpected error: %v", tt.test, err) + } else if !regexp.MustCompile(tt.errRegex).MatchString(err.Error()) { + t.Errorf("%s: unexpected error message mismatch:\n Expected: %s\n Actual: %s", tt.test, tt.errRegex, err.Error()) + } + } + }() + } +} + +// TestWithExponentialBackoff ensures that the webhook's exponential backoff support works as expected +func TestWithExponentialBackoff(t *testing.T) { + count := 0 // To keep track of the requests + gr := schema.GroupResource{ + Group: "webhook.util.k8s.io", + Resource: "test", + } + + // Handler that will handle all backoff CONDITIONS + ebHandler := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch count++; count { + case 1: + // Timeout error with retry supplied + w.WriteHeader(http.StatusGatewayTimeout) + json.NewEncoder(w).Encode(apierrors.NewServerTimeout(gr, "get", 2)) + case 2: + // Internal server error + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(apierrors.NewInternalError(fmt.Errorf("nope"))) + case 3: + // HTTP error that is not retryable + w.WriteHeader(http.StatusNotAcceptable) + json.NewEncoder(w).Encode(apierrors.NewGenericServerResponse(http.StatusNotAcceptable, "get", gr, "testing", "nope", 0, false)) + case 4: + // Successful request + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{ + "status": "OK", + }) + } + } + + // Create and start a simple HTTPS server + server, err := newTestServer(clientCert, clientKey, caCert, ebHandler) + + if err != nil { + t.Errorf("failed to create server: %v", err) + return + } + + defer server.Close() + + // Create a Kubernetes client configuration file + configFile, err := newKubeConfigFile(v1.Config{ + Clusters: []v1.NamedCluster{ + { + Cluster: v1.Cluster{ + Server: server.URL, + CertificateAuthorityData: caCert, + }, + }, + }, + AuthInfos: []v1.NamedAuthInfo{ + { + AuthInfo: v1.AuthInfo{ + ClientCertificateData: clientCert, + ClientKeyData: clientKey, + }, + }, + }, + }) + + if err != nil { + t.Errorf("failed to create the client config file: %v", err) + return + } + + defer os.Remove(configFile) + + wh, err := NewGenericWebhook(api.Registry, api.Codecs, configFile, groupVersions, retryBackoff) + + if err != nil { + t.Fatalf("failed to create the webhook: %v", err) + } + + result := wh.WithExponentialBackoff(func() rest.Result { + return wh.RestClient.Get().Do() + }) + + var statusCode int + + result.StatusCode(&statusCode) + + if statusCode != http.StatusNotAcceptable { + t.Errorf("unexpected status code: %d", statusCode) + } + + result = wh.WithExponentialBackoff(func() rest.Result { + return wh.RestClient.Get().Do() + }) + + result.StatusCode(&statusCode) + + if statusCode != http.StatusOK { + t.Errorf("unexpected status code: %d", statusCode) + } +} + +func bootstrapTestDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "") + + if err != nil { + t.Fatal(err) + } + + // The certificates needed on disk for the tests + files := map[string][]byte{ + "ca.pem": caCert, + "client.pem": clientCert, + "client-key.pem": clientKey, + } + + // Write the certificate files to disk or fail + for fileName, fileData := range files { + if err := ioutil.WriteFile(filepath.Join(dir, fileName), fileData, 0400); err != nil { + t.Fatal(err) + } + } + + return dir +} + +func newKubeConfigFile(config v1.Config) (string, error) { + configFile, err := ioutil.TempFile("", "") + + if err != nil { + return "", fmt.Errorf("unable to create the Kubernetes client config file: %v", err) + } + + if err = json.NewEncoder(configFile).Encode(config); err != nil { + return "", fmt.Errorf("unable to write the Kubernetes client configuration to disk: %v", err) + } + + return configFile.Name(), nil +} + +func newTestServer(clientCert, clientKey, caCert []byte, handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, error) { + var tlsConfig *tls.Config + + if clientCert != nil { + cert, err := tls.X509KeyPair(clientCert, clientKey) + + if err != nil { + return nil, err + } + + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + } + + if caCert != nil { + rootCAs := x509.NewCertPool() + + rootCAs.AppendCertsFromPEM(caCert) + + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + + tlsConfig.ClientCAs = rootCAs + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + if handler == nil { + handler = func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) + } + } + + server := httptest.NewUnstartedServer(http.HandlerFunc(handler)) + + server.TLS = tlsConfig + server.StartTLS() + + return server, nil +}