@@ -16,17 +16,21 @@ package integration
16
16
17
17
import (
18
18
"bytes"
19
+ "crypto/tls"
19
20
"fmt"
21
+ "io/ioutil"
20
22
"math/rand"
21
23
"os"
22
24
"reflect"
23
25
"testing"
24
26
"time"
25
27
28
+ "github.com/coreos/etcd/clientv3"
26
29
"github.com/coreos/etcd/etcdserver/api/v3rpc"
27
30
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
28
31
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
29
32
"github.com/coreos/etcd/pkg/testutil"
33
+
30
34
"golang.org/x/net/context"
31
35
"google.golang.org/grpc"
32
36
"google.golang.org/grpc/metadata"
@@ -1374,6 +1378,207 @@ func TestTLSGRPCAcceptSecureAll(t *testing.T) {
1374
1378
}
1375
1379
}
1376
1380
1381
+ // TestTLSReloadAtomicReplace ensures server reloads expired/valid certs
1382
+ // when all certs are atomically replaced by directory renaming.
1383
+ // And expects server to reject client requests, and vice versa.
1384
+ func TestTLSReloadAtomicReplace (t * testing.T ) {
1385
+ defer testutil .AfterTest (t )
1386
+
1387
+ // clone valid,expired certs to separate directories for atomic renaming
1388
+ vDir , err := ioutil .TempDir (os .TempDir (), "fixtures-valid" )
1389
+ if err != nil {
1390
+ t .Fatal (err )
1391
+ }
1392
+ defer os .RemoveAll (vDir )
1393
+ ts , err := copyTLSFiles (testTLSInfo , vDir )
1394
+ if err != nil {
1395
+ t .Fatal (err )
1396
+ }
1397
+ eDir , err := ioutil .TempDir (os .TempDir (), "fixtures-expired" )
1398
+ if err != nil {
1399
+ t .Fatal (err )
1400
+ }
1401
+ defer os .RemoveAll (eDir )
1402
+ if _ , err = copyTLSFiles (testTLSInfoExpired , eDir ); err != nil {
1403
+ t .Fatal (err )
1404
+ }
1405
+
1406
+ tDir , err := ioutil .TempDir (os .TempDir (), "fixtures" )
1407
+ if err != nil {
1408
+ t .Fatal (err )
1409
+ }
1410
+ os .RemoveAll (tDir )
1411
+ defer os .RemoveAll (tDir )
1412
+
1413
+ // start with valid certs
1414
+ clus := NewClusterV3 (t , & ClusterConfig {Size : 1 , PeerTLS : & ts , ClientTLS : & ts })
1415
+ defer clus .Terminate (t )
1416
+
1417
+ // concurrent client dialing while certs transition from valid to expired
1418
+ errc := make (chan error , 1 )
1419
+ go func () {
1420
+ for {
1421
+ cc , err := ts .ClientConfig ()
1422
+ if err != nil {
1423
+ if os .IsNotExist (err ) {
1424
+ // from concurrent renaming
1425
+ continue
1426
+ }
1427
+ t .Fatal (err )
1428
+ }
1429
+ cli , cerr := clientv3 .New (clientv3.Config {
1430
+ Endpoints : []string {clus .Members [0 ].GRPCAddr ()},
1431
+ DialTimeout : time .Second ,
1432
+ TLS : cc ,
1433
+ })
1434
+ if cerr != nil {
1435
+ errc <- cerr
1436
+ return
1437
+ }
1438
+ cli .Close ()
1439
+ }
1440
+ }()
1441
+
1442
+ // replace certs directory with expired ones
1443
+ if err = os .Rename (vDir , tDir ); err != nil {
1444
+ t .Fatal (err )
1445
+ }
1446
+ if err = os .Rename (eDir , vDir ); err != nil {
1447
+ t .Fatal (err )
1448
+ }
1449
+
1450
+ // after rename,
1451
+ // 'vDir' contains expired certs
1452
+ // 'tDir' contains valid certs
1453
+ // 'eDir' does not exist
1454
+
1455
+ select {
1456
+ case err = <- errc :
1457
+ if err != grpc .ErrClientConnTimeout {
1458
+ t .Fatalf ("expected %v, got %v" , grpc .ErrClientConnTimeout , err )
1459
+ }
1460
+ case <- time .After (5 * time .Second ):
1461
+ t .Fatal ("failed to receive dial timeout error" )
1462
+ }
1463
+
1464
+ // now, replace expired certs back with valid ones
1465
+ if err = os .Rename (tDir , eDir ); err != nil {
1466
+ t .Fatal (err )
1467
+ }
1468
+ if err = os .Rename (vDir , tDir ); err != nil {
1469
+ t .Fatal (err )
1470
+ }
1471
+ if err = os .Rename (eDir , vDir ); err != nil {
1472
+ t .Fatal (err )
1473
+ }
1474
+
1475
+ // new incoming client request should trigger
1476
+ // listener to reload valid certs
1477
+ var tls * tls.Config
1478
+ tls , err = ts .ClientConfig ()
1479
+ if err != nil {
1480
+ t .Fatal (err )
1481
+ }
1482
+ var cl * clientv3.Client
1483
+ cl , err = clientv3 .New (clientv3.Config {
1484
+ Endpoints : []string {clus .Members [0 ].GRPCAddr ()},
1485
+ DialTimeout : time .Second ,
1486
+ TLS : tls ,
1487
+ })
1488
+ if err != nil {
1489
+ t .Fatalf ("expected no error, got %v" , err )
1490
+ }
1491
+ cl .Close ()
1492
+ }
1493
+
1494
+ // TestTLSReloadCopy ensures server reloads expired/valid certs
1495
+ // when new certs are copied over, one by one. And expects server
1496
+ // to reject client requests, and vice versa.
1497
+ func TestTLSReloadCopy (t * testing.T ) {
1498
+ defer testutil .AfterTest (t )
1499
+
1500
+ // clone certs directory, free to overwrite
1501
+ cDir , err := ioutil .TempDir (os .TempDir (), "fixtures-test" )
1502
+ if err != nil {
1503
+ t .Fatal (err )
1504
+ }
1505
+ defer os .RemoveAll (cDir )
1506
+ ts , err := copyTLSFiles (testTLSInfo , cDir )
1507
+ if err != nil {
1508
+ t .Fatal (err )
1509
+ }
1510
+
1511
+ // start with valid certs
1512
+ clus := NewClusterV3 (t , & ClusterConfig {Size : 1 , PeerTLS : & ts , ClientTLS : & ts })
1513
+ defer clus .Terminate (t )
1514
+
1515
+ // concurrent client dialing while certs transition from valid to expired
1516
+ errc := make (chan error , 1 )
1517
+ go func () {
1518
+ for {
1519
+ cc , err := ts .ClientConfig ()
1520
+ if err != nil {
1521
+ // from concurrent certs overwriting
1522
+ switch err .Error () {
1523
+ case "tls: private key does not match public key" :
1524
+ fallthrough
1525
+ case "tls: failed to find any PEM data in key input" :
1526
+ continue
1527
+ }
1528
+ t .Fatal (err )
1529
+ }
1530
+ cli , cerr := clientv3 .New (clientv3.Config {
1531
+ Endpoints : []string {clus .Members [0 ].GRPCAddr ()},
1532
+ DialTimeout : time .Second ,
1533
+ TLS : cc ,
1534
+ })
1535
+ if cerr != nil {
1536
+ errc <- cerr
1537
+ return
1538
+ }
1539
+ cli .Close ()
1540
+ }
1541
+ }()
1542
+
1543
+ // overwrite valid certs with expired ones
1544
+ // (e.g. simulate cert expiration in practice)
1545
+ if _ , err = copyTLSFiles (testTLSInfoExpired , cDir ); err != nil {
1546
+ t .Fatal (err )
1547
+ }
1548
+
1549
+ select {
1550
+ case gerr := <- errc :
1551
+ if gerr != grpc .ErrClientConnTimeout {
1552
+ t .Fatalf ("expected %v, got %v" , grpc .ErrClientConnTimeout , gerr )
1553
+ }
1554
+ case <- time .After (5 * time .Second ):
1555
+ t .Fatal ("failed to receive dial timeout error" )
1556
+ }
1557
+
1558
+ // now, replace expired certs back with valid ones
1559
+ if _ , err = copyTLSFiles (testTLSInfo , cDir ); err != nil {
1560
+ t .Fatal (err )
1561
+ }
1562
+
1563
+ // new incoming client request should trigger
1564
+ // listener to reload valid certs
1565
+ var tls * tls.Config
1566
+ tls , err = ts .ClientConfig ()
1567
+ if err != nil {
1568
+ t .Fatal (err )
1569
+ }
1570
+ var cl * clientv3.Client
1571
+ cl , err = clientv3 .New (clientv3.Config {
1572
+ Endpoints : []string {clus .Members [0 ].GRPCAddr ()},
1573
+ DialTimeout : time .Second ,
1574
+ TLS : tls ,
1575
+ })
1576
+ if err != nil {
1577
+ t .Fatalf ("expected no error, got %v" , err )
1578
+ }
1579
+ cl .Close ()
1580
+ }
1581
+
1377
1582
func TestGRPCRequireLeader (t * testing.T ) {
1378
1583
defer testutil .AfterTest (t )
1379
1584
0 commit comments