Skip to content
This repository was archived by the owner on Jul 30, 2021. It is now read-only.

Commit 96ff91e

Browse files
committed
SHE: auto-detect TLS and create secure etcd client
1 parent 40d9e3d commit 96ff91e

File tree

4 files changed

+84
-24
lines changed

4 files changed

+84
-24
lines changed

cmd/bootkube/recover.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ func runCmdRecover(cmd *cobra.Command, args []string) error {
8787

8888
bootkube.UserOutput("Waiting for etcd server to start...\n")
8989

90-
err = etcdutil.WaitClusterReady(recovery.RecoveryEtcdClientAddr)
90+
// TODO: add self-hosted etcd+TLS support?
91+
err = etcdutil.WaitClusterReady(recovery.RecoveryEtcdClientAddr, nil)
9192
if err != nil {
9293
return err
9394
}

pkg/bootkube/bootkube.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"path/filepath"
77
"time"
88

9-
"k8s.io/client-go/tools/clientcmd"
10-
119
"github.com/kubernetes-incubator/bootkube/pkg/asset"
1210
"github.com/kubernetes-incubator/bootkube/pkg/util/etcdutil"
11+
12+
"k8s.io/client-go/tools/clientcmd"
1313
)
1414

1515
const assetTimeout = 20 * time.Minute
@@ -87,7 +87,11 @@ func (b *bootkube) Run() error {
8787

8888
if selfHostedEtcd {
8989
UserOutput("Migrating to self-hosted etcd cluster...\n")
90-
if err = etcdutil.Migrate(kubeConfig, filepath.Join(b.assetDir, asset.AssetPathBootstrapEtcdService), filepath.Join(b.assetDir, asset.AssetPathMigrateEtcdCluster)); err != nil {
90+
91+
svcPath := filepath.Join(b.assetDir, asset.AssetPathBootstrapEtcdService)
92+
tprPath := filepath.Join(b.assetDir, asset.AssetPathMigrateEtcdCluster)
93+
err = etcdutil.Migrate(kubeConfig, b.assetDir, svcPath, tprPath)
94+
if err != nil {
9195
return err
9296
}
9397
}

pkg/util/etcdutil/migrate.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package etcdutil
22

33
import (
44
"context"
5+
"crypto/tls"
56
"encoding/json"
67
"errors"
78
"fmt"
89
"io/ioutil"
10+
"os"
11+
"path/filepath"
912
"time"
1013

1114
"github.com/kubernetes-incubator/bootkube/pkg/asset"
@@ -32,7 +35,19 @@ var (
3235
pollTimeout = 300 * time.Second
3336
)
3437

35-
func Migrate(kubeConfig clientcmd.ClientConfig, svcPath, tprPath string) error {
38+
func Migrate(kubeConfig clientcmd.ClientConfig, assetDir, svcPath, tprPath string) error {
39+
useEtcdTLS, err := detectEtcdTLS(assetDir)
40+
if err != nil {
41+
return err
42+
}
43+
var etcdTLS *tls.Config
44+
if useEtcdTLS {
45+
etcdTLS, err = makeTLSConfig(assetDir)
46+
if err != nil {
47+
return err
48+
}
49+
}
50+
3651
config, err := kubeConfig.ClientConfig()
3752
if err != nil {
3853
return fmt.Errorf("failed to create kube client config: %v", err)
@@ -49,7 +64,7 @@ func Migrate(kubeConfig clientcmd.ClientConfig, svcPath, tprPath string) error {
4964
}
5065
glog.Infof("created etcd cluster TPR")
5166

52-
if err := createBootstrapEtcdService(kubecli, svcPath); err != nil {
67+
if err := createBootstrapEtcdService(kubecli, etcdTLS, svcPath); err != nil {
5368
return fmt.Errorf("failed to create bootstrap-etcd-service: %v", err)
5469
}
5570
defer cleanupBootstrapEtcdService(kubecli)
@@ -70,7 +85,7 @@ func Migrate(kubeConfig clientcmd.ClientConfig, svcPath, tprPath string) error {
7085
}
7186
glog.Info("etcd cluster for migration is now running")
7287

73-
if err := waitBootEtcdRemoved(etcdServiceIP); err != nil {
88+
if err := waitBootEtcdRemoved(etcdServiceIP, etcdTLS); err != nil {
7489
return fmt.Errorf("failed to wait for boot-etcd to be removed: %v", err)
7590
}
7691
glog.Info("removed boot-etcd from the etcd cluster")
@@ -99,7 +114,7 @@ func waitEtcdTPRReady(restClient restclient.Interface, ns string) error {
99114
return nil
100115
}
101116

102-
func createBootstrapEtcdService(kubecli kubernetes.Interface, svcPath string) error {
117+
func createBootstrapEtcdService(kubecli kubernetes.Interface, etcdTLS *tls.Config, svcPath string) error {
103118
// Create the service.
104119
svcb, err := ioutil.ReadFile(svcPath)
105120
if err != nil {
@@ -115,8 +130,12 @@ func createBootstrapEtcdService(kubecli kubernetes.Interface, svcPath string) er
115130
return err
116131
}
117132

133+
scheme := "http://"
134+
if etcdTLS != nil {
135+
scheme = "https://"
136+
}
118137
// Wait for the service to be reachable (sometimes this takes a little while).
119-
if err := WaitClusterReady("http://" + svc.Spec.ClusterIP + ":12379"); err != nil {
138+
if err := WaitClusterReady(scheme+svc.Spec.ClusterIP+":12379", etcdTLS); err != nil {
120139
return fmt.Errorf("timed out waiting for bootstrap etcd service: %s", err)
121140
}
122141
return nil
@@ -165,12 +184,18 @@ func getServiceIP(kubecli kubernetes.Interface, ns, svcName string) (string, err
165184
return svc.Spec.ClusterIP, nil
166185
}
167186

168-
func waitBootEtcdRemoved(etcdServiceIP string) error {
187+
func waitBootEtcdRemoved(etcdServiceIP string, etcdTLS *tls.Config) error {
188+
scheme := "http"
189+
if etcdTLS != nil {
190+
scheme = "https"
191+
}
192+
cfg := clientv3.Config{
193+
Endpoints: []string{fmt.Sprintf("%s://%s:2379", scheme, etcdServiceIP)},
194+
TLS: etcdTLS,
195+
DialTimeout: 5 * time.Second,
196+
}
197+
169198
err := wait.Poll(pollInterval, pollTimeout, func() (bool, error) {
170-
cfg := clientv3.Config{
171-
Endpoints: []string{fmt.Sprintf("http://%s:2379", etcdServiceIP)},
172-
DialTimeout: 5 * time.Second,
173-
}
174199
etcdcli, err := clientv3.New(cfg)
175200
if err != nil {
176201
glog.Errorf("failed to create etcd client, will retry: %v", err)
@@ -210,3 +235,15 @@ func cleanupBootstrapEtcdService(kubecli kubernetes.Interface) {
210235
glog.Errorf("timed out removing bootstrap-etcd-service: %v", err)
211236
}
212237
}
238+
239+
func detectEtcdTLS(assetDir string) (bool, error) {
240+
etcdCAAssetPath := filepath.Join(assetDir, asset.AssetPathEtcdCA)
241+
_, err := os.Stat(etcdCAAssetPath)
242+
if err == nil {
243+
return true, nil
244+
}
245+
if os.IsNotExist(err) {
246+
return false, nil
247+
}
248+
return false, err
249+
}

pkg/util/etcdutil/util.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,48 @@
11
package etcdutil
22

33
import (
4-
"fmt"
5-
"io/ioutil"
6-
"net/http"
4+
"context"
5+
"crypto/tls"
6+
"path/filepath"
77

8+
"github.com/coreos/etcd/clientv3"
9+
"github.com/coreos/etcd/pkg/transport"
810
"github.com/golang/glog"
11+
"github.com/kubernetes-incubator/bootkube/pkg/asset"
912
"k8s.io/apimachinery/pkg/util/wait"
1013
)
1114

1215
// WaitClusterReady waits the etcd server ready to serve client requests.
13-
func WaitClusterReady(endpoint string) error {
16+
func WaitClusterReady(endpoint string, etcdTLS *tls.Config) error {
17+
cfg := clientv3.Config{
18+
Endpoints: []string{endpoint},
19+
TLS: etcdTLS,
20+
DialTimeout: pollInterval,
21+
}
1422
err := wait.Poll(pollInterval, pollTimeout, func() (bool, error) {
15-
resp, err := http.Get(fmt.Sprintf("%s/version", endpoint))
23+
etcdcli, err := clientv3.New(cfg)
1624
if err != nil {
17-
glog.Infof("could not read from etcd: %v", err)
25+
glog.Infof("could not create etcd client, retrying later: %v", err)
1826
return false, nil
1927
}
20-
defer resp.Body.Close()
21-
body, err := ioutil.ReadAll(resp.Body)
22-
if len(body) == 0 || err != nil {
23-
glog.Infof("could not read from etcd: %v", err)
28+
ctx, cancel := context.WithTimeout(context.Background(), pollInterval)
29+
_, err = etcdcli.Get(ctx, "/")
30+
cancel()
31+
if err != nil {
32+
glog.Infof("could not read from etcd, retrying later: %v", err)
2433
return false, nil
2534
}
2635
return true, nil
2736
})
2837

2938
return err
3039
}
40+
41+
func makeTLSConfig(assetDir string) (*tls.Config, error) {
42+
tlsInfo := transport.TLSInfo{
43+
TrustedCAFile: filepath.Join(assetDir, asset.AssetPathEtcdCA),
44+
CertFile: filepath.Join(assetDir, asset.AssetPathEtcdClientCert),
45+
KeyFile: filepath.Join(assetDir, asset.AssetPathEtcdClientKey),
46+
}
47+
return tlsInfo.ClientConfig()
48+
}

0 commit comments

Comments
 (0)