@@ -261,6 +261,7 @@ type vm struct {
261261 keys io.WriteCloser
262262 serial * serialLog
263263 trust string
264+ leaf []byte
264265 admin admin
265266 stopped bool
266267}
@@ -370,10 +371,11 @@ func (v *vm) startTPM(t *testing.T) string {
370371}
371372
372373// waitUp waits for the management listener after a boot that began at
373- // started and re-pins the node's server certificate. The node mints a new one
374- // on every boot, so a certificate other than prev means this boot is serving;
375- // prev is empty for a first boot. The serial log is not used for this: init
376- // logs through /dev/kmsg, which the kernel rate-limits, so a line can be lost.
374+ // started and re-pins the node's trust. The node mints a new server
375+ // certificate on every boot, so a leaf other than prev means this boot is
376+ // serving; prev is empty for a first boot. The serial log is not used for
377+ // this: init logs through /dev/kmsg, which the kernel rate-limits, so a line
378+ // can be lost.
377379func (v * vm ) waitUp (t * testing.T , prev []byte , started time.Time , what string ) {
378380 t .Helper ()
379381 limit := 3 * time .Minute
@@ -382,27 +384,56 @@ func (v *vm) waitUp(t *testing.T, prev []byte, started time.Time, what string) {
382384 }
383385 addr := "127.0.0.1:" + v .mgmtPort
384386 deadline := time .Now ().Add (limit )
385- var cert []byte
387+ var chain []* x509. Certificate
386388 for {
387- cert = probeServerCert (addr )
388- if cert != nil && ! bytes .Equal (cert , prev ) {
389+ chain = probeServerChain (addr )
390+ if chain != nil && ! bytes .Equal (certPEM ( chain [ 0 ]) , prev ) {
389391 break
390392 }
391393 if time .Now ().After (deadline ) {
392394 t .Fatalf ("%s: %s did not bring the management API up within %s:\n %s" , v .name , what , limit , lastLines (v .serial .String (), 60 ))
393395 }
394396 time .Sleep (time .Second )
395397 }
396- v .trust = filepath .Join (v .dir , "trust.crt" )
397- writeFile (t , v .trust , cert )
398+ v .pin (t , chain )
398399 took := time .Since (started ).Round (time .Second )
399400 t .Logf ("%s: %s up in %s" , v .name , what , took )
400401 v .s .timing ("%s %s: management API up %s after %s" , v .name , what , took , map [bool ]string {true : "QEMU start" , false : "the reboot request" }[prev == nil ])
401402}
402403
403- // probeServerCert returns the PEM of the certificate the listener at addr
404- // presents, or nil while it does not answer.
405- func probeServerCert (addr string ) []byte {
404+ // pin trusts the last certificate the listener presented: the self-signed
405+ // leaf before the node has a CA, and the root its CA-signed leaf chains to
406+ // after. The leaf is kept to tell the next boot's certificate apart.
407+ func (v * vm ) pin (t * testing.T , chain []* x509.Certificate ) {
408+ t .Helper ()
409+ v .leaf = certPEM (chain [0 ])
410+ v .trust = filepath .Join (v .dir , "trust.crt" )
411+ writeFile (t , v .trust , certPEM (chain [len (chain )- 1 ]))
412+ }
413+
414+ // trustCA waits for the listener to present a leaf signed by the node's new
415+ // CA, which it switches to without a restart once the CA is committed, and
416+ // trusts the root that leaf chains to.
417+ func (v * vm ) trustCA (t * testing.T ) {
418+ t .Helper ()
419+ addr := "127.0.0.1:" + v .mgmtPort
420+ deadline := time .Now ().Add (2 * time .Minute )
421+ for {
422+ chain := probeServerChain (addr )
423+ if len (chain ) > 1 && chain [len (chain )- 1 ].IsCA && chain [0 ].CheckSignatureFrom (chain [1 ]) == nil {
424+ v .pin (t , chain )
425+ return
426+ }
427+ if time .Now ().After (deadline ) {
428+ t .Fatalf ("%s: the management listener did not switch to a CA-signed certificate within 2m" , v .name )
429+ }
430+ time .Sleep (time .Second )
431+ }
432+ }
433+
434+ // probeServerChain returns the certificates the listener at addr presents,
435+ // leaf first, or nil while it does not answer.
436+ func probeServerChain (addr string ) []* x509.Certificate {
406437 conn , err := tls .DialWithDialer (& net.Dialer {Timeout : 2 * time .Second }, "tcp" , addr ,
407438 & tls.Config {InsecureSkipVerify : true , MinVersion : tls .VersionTLS13 }) //nolint:gosec // pinning grab
408439 if err != nil {
@@ -413,7 +444,11 @@ func probeServerCert(addr string) []byte {
413444 if len (certs ) == 0 {
414445 return nil
415446 }
416- return pem .EncodeToMemory (& pem.Block {Type : "CERTIFICATE" , Bytes : certs [0 ].Raw })
447+ return certs
448+ }
449+
450+ func certPEM (c * x509.Certificate ) []byte {
451+ return pem .EncodeToMemory (& pem.Block {Type : "CERTIFICATE" , Bytes : c .Raw })
417452}
418453
419454// ctl runs cryptosctl against the node as the bootstrap admin. The binary is
@@ -422,7 +457,7 @@ func (v *vm) ctl(stdin string, args ...string) (string, error) {
422457 full := append ([]string {
423458 "--endpoint" , "127.0.0.1:" + v .mgmtPort ,
424459 "--identity" , v .admin .cert , "--identity-key" , v .admin .key ,
425- "--trust" , v .trust , "--server-name" , "localhost" ,
460+ "--trust" , v .trust , "--server-name" , suiteNodeAddr ,
426461 }, args ... )
427462 ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Minute )
428463 defer cancel ()
@@ -462,10 +497,7 @@ func (v *vm) status(t *testing.T) string {
462497// come back.
463498func (v * vm ) reboot (t * testing.T , cn string ) {
464499 t .Helper ()
465- prev , err := os .ReadFile (v .trust )
466- if err != nil {
467- t .Fatalf ("%s: read the pinned server certificate: %v" , v .name , err )
468- }
500+ prev := v .leaf
469501 bootsBefore := bootCount (v .status (t ))
470502 started := time .Now ()
471503 out := v .mustCtl (t , "reboot" , "--confirm" , cn )
@@ -859,6 +891,7 @@ func stepRootCeremony(t *testing.T, s *suite, st *suiteState) {
859891 t .Fatalf ("ceremony output missing %q:\n %s" , want , out )
860892 }
861893 }
894+ st .root .trustCA (t )
862895 st .rootPEM = st .root .mustCtl (t , "identity" , "show" , "-o" , "pem" )
863896 st .rootCert = parsePEMCerts (t , []byte (st .rootPEM ))[0 ]
864897 writeFile (t , filepath .Join (s .env .out , "root.pem" ), []byte (st .rootPEM ))
@@ -895,6 +928,7 @@ func stepIntermediate(t *testing.T, s *suite, st *suiteState) {
895928 chainPath := filepath .Join (st .inter .dir , "subordinate-chain.pem" )
896929 writeFile (t , chainPath , []byte (chain ))
897930 st .inter .mustCtl (t , "ca" , "submit-subordinate-cert" , "--chain" , chainPath )
931+ st .inter .trustCA (t )
898932
899933 st .intPEM = st .inter .mustCtl (t , "identity" , "show" , "-o" , "pem" )
900934 certs := parsePEMCerts (t , []byte (st .intPEM ))
@@ -1325,7 +1359,7 @@ func stepUpgrade(t *testing.T, s *suite, st *suiteState, v *vm, cn string) {
13251359 if ! strings .Contains (staged , "Reboot pending: yes" ) {
13261360 t .Fatalf ("%s: after staging, want a reboot pending:\n %s" , v .name , staged )
13271361 }
1328- prev , _ := os . ReadFile ( v . trust )
1362+ prev := v . leaf
13291363 started := time .Now ()
13301364 v .mustCtl (t , "image" , "activate" , "--confirm" , cn )
13311365 v .waitUp (t , prev , started , "upgrade" )
@@ -1406,6 +1440,7 @@ func stepEscrowImport(t *testing.T, s *suite, st *suiteState) {
14061440 if out , err := st .spare .ctl (st .backupPwd + "\n " , "ca" , "import-key" , "--backup" , st .backup ); err != nil {
14071441 t .Fatalf ("import the Root backup: %v\n %s" , err , out )
14081442 }
1443+ st .spare .trustCA (t )
14091444 got := parsePEMCerts (t , []byte (st .spare .mustCtl (t , "identity" , "show" , "-o" , "pem" )))[0 ]
14101445 if ! bytes .Equal (got .Raw , st .rootCert .Raw ) {
14111446 t .Fatalf ("the restored node holds %s (serial %s), not the Root" , got .Subject , got .SerialNumber .Text (16 ))
0 commit comments