server_bootstrap_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package app
  14. import (
  15. "crypto/ecdsa"
  16. "crypto/elliptic"
  17. "crypto/rand"
  18. "crypto/x509"
  19. "crypto/x509/pkix"
  20. "encoding/json"
  21. "encoding/pem"
  22. "io/ioutil"
  23. "math/big"
  24. "net/http"
  25. "net/http/httptest"
  26. "os"
  27. "path/filepath"
  28. "sync"
  29. "testing"
  30. "time"
  31. cfsslconfig "github.com/cloudflare/cfssl/config"
  32. cfsslsigner "github.com/cloudflare/cfssl/signer"
  33. cfssllocal "github.com/cloudflare/cfssl/signer/local"
  34. certapi "k8s.io/api/certificates/v1beta1"
  35. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  36. "k8s.io/apimachinery/pkg/runtime"
  37. "k8s.io/apimachinery/pkg/types"
  38. restclient "k8s.io/client-go/rest"
  39. certutil "k8s.io/client-go/util/cert"
  40. )
  41. // Test_buildClientCertificateManager validates that we can build a local client cert
  42. // manager that will use the bootstrap client until we get a valid cert, then use our
  43. // provided identity on subsequent requests.
  44. func Test_buildClientCertificateManager(t *testing.T) {
  45. testDir, err := ioutil.TempDir("", "kubeletcert")
  46. if err != nil {
  47. t.Fatal(err)
  48. }
  49. defer func() { os.RemoveAll(testDir) }()
  50. serverPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  51. if err != nil {
  52. t.Fatal(err)
  53. }
  54. serverCA, err := certutil.NewSelfSignedCACert(certutil.Config{
  55. CommonName: "the-test-framework",
  56. }, serverPrivateKey)
  57. if err != nil {
  58. t.Fatal(err)
  59. }
  60. server := &csrSimulator{
  61. t: t,
  62. serverPrivateKey: serverPrivateKey,
  63. serverCA: serverCA,
  64. }
  65. s := httptest.NewServer(server)
  66. defer s.Close()
  67. config1 := &restclient.Config{
  68. UserAgent: "FirstClient",
  69. Host: s.URL,
  70. }
  71. config2 := &restclient.Config{
  72. UserAgent: "SecondClient",
  73. Host: s.URL,
  74. }
  75. nodeName := types.NodeName("test")
  76. m, err := buildClientCertificateManager(config1, config2, testDir, nodeName)
  77. if err != nil {
  78. t.Fatal(err)
  79. }
  80. defer m.Stop()
  81. r := m.(rotater)
  82. // get an expired CSR (simulating historical output)
  83. server.backdate = 2 * time.Hour
  84. server.SetExpectUserAgent("FirstClient")
  85. ok, err := r.RotateCerts()
  86. if !ok || err != nil {
  87. t.Fatalf("unexpected rotation err: %t %v", ok, err)
  88. }
  89. if cert := m.Current(); cert != nil {
  90. t.Fatalf("Unexpected cert, should be expired: %#v", cert)
  91. }
  92. fi := getFileInfo(testDir)
  93. if len(fi) != 2 {
  94. t.Fatalf("Unexpected directory contents: %#v", fi)
  95. }
  96. // if m.Current() == nil, then we try again and get a valid
  97. // client
  98. server.backdate = 0
  99. server.SetExpectUserAgent("FirstClient")
  100. if ok, err := r.RotateCerts(); !ok || err != nil {
  101. t.Fatalf("unexpected rotation err: %t %v", ok, err)
  102. }
  103. if cert := m.Current(); cert == nil {
  104. t.Fatalf("Unexpected cert, should be valid: %#v", cert)
  105. }
  106. fi = getFileInfo(testDir)
  107. if len(fi) != 2 {
  108. t.Fatalf("Unexpected directory contents: %#v", fi)
  109. }
  110. // if m.Current() != nil, then we should use the second client
  111. server.SetExpectUserAgent("SecondClient")
  112. if ok, err := r.RotateCerts(); !ok || err != nil {
  113. t.Fatalf("unexpected rotation err: %t %v", ok, err)
  114. }
  115. if cert := m.Current(); cert == nil {
  116. t.Fatalf("Unexpected cert, should be valid: %#v", cert)
  117. }
  118. fi = getFileInfo(testDir)
  119. if len(fi) != 2 {
  120. t.Fatalf("Unexpected directory contents: %#v", fi)
  121. }
  122. }
  123. func Test_buildClientCertificateManager_populateCertDir(t *testing.T) {
  124. testDir, err := ioutil.TempDir("", "kubeletcert")
  125. if err != nil {
  126. t.Fatal(err)
  127. }
  128. defer func() { os.RemoveAll(testDir) }()
  129. // when no cert is provided, write nothing to disk
  130. config1 := &restclient.Config{
  131. UserAgent: "FirstClient",
  132. Host: "http://localhost",
  133. }
  134. config2 := &restclient.Config{
  135. UserAgent: "SecondClient",
  136. Host: "http://localhost",
  137. }
  138. nodeName := types.NodeName("test")
  139. if _, err := buildClientCertificateManager(config1, config2, testDir, nodeName); err != nil {
  140. t.Fatal(err)
  141. }
  142. fi := getFileInfo(testDir)
  143. if len(fi) != 0 {
  144. t.Fatalf("Unexpected directory contents: %#v", fi)
  145. }
  146. // an invalid cert should be ignored
  147. config2.CertData = []byte("invalid contents")
  148. config2.KeyData = []byte("invalid contents")
  149. if _, err := buildClientCertificateManager(config1, config2, testDir, nodeName); err == nil {
  150. t.Fatal("unexpected non error")
  151. }
  152. fi = getFileInfo(testDir)
  153. if len(fi) != 0 {
  154. t.Fatalf("Unexpected directory contents: %#v", fi)
  155. }
  156. // an expired client certificate should be written to disk, because the cert manager can
  157. // use config1 to refresh it and the cert manager won't return it for clients.
  158. config2.CertData, config2.KeyData = genClientCert(t, time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour))
  159. if _, err := buildClientCertificateManager(config1, config2, testDir, nodeName); err != nil {
  160. t.Fatal(err)
  161. }
  162. fi = getFileInfo(testDir)
  163. if len(fi) != 2 {
  164. t.Fatalf("Unexpected directory contents: %#v", fi)
  165. }
  166. // a valid, non-expired client certificate should be written to disk
  167. config2.CertData, config2.KeyData = genClientCert(t, time.Now().Add(-time.Hour), time.Now().Add(24*time.Hour))
  168. if _, err := buildClientCertificateManager(config1, config2, testDir, nodeName); err != nil {
  169. t.Fatal(err)
  170. }
  171. fi = getFileInfo(testDir)
  172. if len(fi) != 2 {
  173. t.Fatalf("Unexpected directory contents: %#v", fi)
  174. }
  175. }
  176. func getFileInfo(dir string) map[string]os.FileInfo {
  177. fi := make(map[string]os.FileInfo)
  178. filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  179. if path == dir {
  180. return nil
  181. }
  182. fi[path] = info
  183. if !info.IsDir() {
  184. os.Remove(path)
  185. }
  186. return nil
  187. })
  188. return fi
  189. }
  190. type rotater interface {
  191. RotateCerts() (bool, error)
  192. }
  193. func getCSR(req *http.Request) (*certapi.CertificateSigningRequest, error) {
  194. if req.Body == nil {
  195. return nil, nil
  196. }
  197. body, err := ioutil.ReadAll(req.Body)
  198. if err != nil {
  199. return nil, err
  200. }
  201. csr := &certapi.CertificateSigningRequest{}
  202. if err := json.Unmarshal(body, csr); err != nil {
  203. return nil, err
  204. }
  205. return csr, nil
  206. }
  207. func mustMarshal(obj interface{}) []byte {
  208. data, err := json.Marshal(obj)
  209. if err != nil {
  210. panic(err)
  211. }
  212. return data
  213. }
  214. type csrSimulator struct {
  215. t *testing.T
  216. serverPrivateKey *ecdsa.PrivateKey
  217. serverCA *x509.Certificate
  218. backdate time.Duration
  219. userAgentLock sync.Mutex
  220. expectUserAgent string
  221. lock sync.Mutex
  222. csr *certapi.CertificateSigningRequest
  223. }
  224. func (s *csrSimulator) SetExpectUserAgent(a string) {
  225. s.userAgentLock.Lock()
  226. defer s.userAgentLock.Unlock()
  227. s.expectUserAgent = a
  228. }
  229. func (s *csrSimulator) ExpectUserAgent() string {
  230. s.userAgentLock.Lock()
  231. defer s.userAgentLock.Unlock()
  232. return s.expectUserAgent
  233. }
  234. func (s *csrSimulator) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  235. s.lock.Lock()
  236. defer s.lock.Unlock()
  237. t := s.t
  238. // filter out timeouts as csrSimulator don't support them
  239. q := req.URL.Query()
  240. q.Del("timeout")
  241. q.Del("timeoutSeconds")
  242. q.Del("allowWatchBookmarks")
  243. req.URL.RawQuery = q.Encode()
  244. t.Logf("Request %q %q %q", req.Method, req.URL, req.UserAgent())
  245. if a := s.ExpectUserAgent(); len(a) > 0 && req.UserAgent() != a {
  246. t.Errorf("Unexpected user agent: %s", req.UserAgent())
  247. }
  248. switch {
  249. case req.Method == "POST" && req.URL.Path == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests":
  250. csr, err := getCSR(req)
  251. if err != nil {
  252. t.Fatal(err)
  253. }
  254. if csr.Name == "" {
  255. csr.Name = "test-csr"
  256. }
  257. csr.UID = types.UID("1")
  258. csr.ResourceVersion = "1"
  259. data := mustMarshal(csr)
  260. w.Header().Set("Content-Type", "application/json")
  261. w.Write(data)
  262. csr = csr.DeepCopy()
  263. csr.ResourceVersion = "2"
  264. var usages []string
  265. for _, usage := range csr.Spec.Usages {
  266. usages = append(usages, string(usage))
  267. }
  268. policy := &cfsslconfig.Signing{
  269. Default: &cfsslconfig.SigningProfile{
  270. Usage: usages,
  271. Expiry: time.Hour,
  272. ExpiryString: time.Hour.String(),
  273. Backdate: s.backdate,
  274. },
  275. }
  276. cfs, err := cfssllocal.NewSigner(s.serverPrivateKey, s.serverCA, cfsslsigner.DefaultSigAlgo(s.serverPrivateKey), policy)
  277. if err != nil {
  278. t.Fatal(err)
  279. }
  280. csr.Status.Certificate, err = cfs.Sign(cfsslsigner.SignRequest{
  281. Request: string(csr.Spec.Request),
  282. })
  283. if err != nil {
  284. t.Fatal(err)
  285. }
  286. csr.Status.Conditions = []certapi.CertificateSigningRequestCondition{
  287. {Type: certapi.CertificateApproved},
  288. }
  289. s.csr = csr
  290. case req.Method == "GET" && req.URL.Path == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests" && req.URL.RawQuery == "fieldSelector=metadata.name%3Dtest-csr&limit=500&resourceVersion=0":
  291. if s.csr == nil {
  292. t.Fatalf("no csr")
  293. }
  294. csr := s.csr.DeepCopy()
  295. data := mustMarshal(&certapi.CertificateSigningRequestList{
  296. ListMeta: metav1.ListMeta{
  297. ResourceVersion: "2",
  298. },
  299. Items: []certapi.CertificateSigningRequest{
  300. *csr,
  301. },
  302. })
  303. w.Header().Set("Content-Type", "application/json")
  304. w.Write(data)
  305. case req.Method == "GET" && req.URL.Path == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests" && req.URL.RawQuery == "fieldSelector=metadata.name%3Dtest-csr&resourceVersion=2&watch=true":
  306. if s.csr == nil {
  307. t.Fatalf("no csr")
  308. }
  309. csr := s.csr.DeepCopy()
  310. data := mustMarshal(&metav1.WatchEvent{
  311. Type: "ADDED",
  312. Object: runtime.RawExtension{
  313. Raw: mustMarshal(csr),
  314. },
  315. })
  316. w.Header().Set("Content-Type", "application/json")
  317. w.Write(data)
  318. default:
  319. t.Fatalf("unexpected request: %s %s", req.Method, req.URL)
  320. }
  321. }
  322. // genClientCert generates an x509 certificate for testing. Certificate and key
  323. // are returned in PEM encoding.
  324. func genClientCert(t *testing.T, from, to time.Time) ([]byte, []byte) {
  325. key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  326. if err != nil {
  327. t.Fatal(err)
  328. }
  329. keyRaw, err := x509.MarshalECPrivateKey(key)
  330. if err != nil {
  331. t.Fatal(err)
  332. }
  333. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  334. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  335. if err != nil {
  336. t.Fatal(err)
  337. }
  338. cert := &x509.Certificate{
  339. SerialNumber: serialNumber,
  340. Subject: pkix.Name{Organization: []string{"Acme Co"}},
  341. NotBefore: from,
  342. NotAfter: to,
  343. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  344. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  345. BasicConstraintsValid: true,
  346. }
  347. certRaw, err := x509.CreateCertificate(rand.Reader, cert, cert, key.Public(), key)
  348. if err != nil {
  349. t.Fatal(err)
  350. }
  351. return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certRaw}),
  352. pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyRaw})
  353. }