Skip to content

Commit

Permalink
fix: handle bootkube recover correctly, support recovery from etcd
Browse files Browse the repository at this point in the history
Bootkube recover process (and `talosctl recover`) was actually
regenerating assets each time `recover` runs forcing control plane to be
at the state when cluster got created. This PR fixes that by running
recover process correctly.

Recovery via etcd was fixed to handle encrypted etcd data:
it follows the way `apiserver` handles encryption at rest, and as at
the moment AES CBC is the only supported encryption method, code simply
follows the same path.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Aug 18, 2020
1 parent 7fac9d3 commit 6a7cc02
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 80 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ require (
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 // indirect
github.com/insomniacslk/dhcp v0.0.0-20200711001733-e1b69ee5fb33
github.com/jsimonetti/rtnetlink v0.0.0-20200709124027-1aae10735293
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200501183829-d8e5fa33347a
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200817205730-0b4482256ca1
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect
github.com/mdlayher/genetlink v1.0.0
github.com/mdlayher/netlink v1.1.0
Expand Down Expand Up @@ -80,6 +80,7 @@ require (
inet.af/tcpproxy v0.0.0-20200125044825-b6bb9b5b8252
k8s.io/api v0.19.0-rc.3
k8s.io/apimachinery v0.19.0-rc.3
k8s.io/apiserver v0.19.0-rc.3
k8s.io/client-go v0.19.0-rc.3
k8s.io/cri-api v0.19.0-rc.3
k8s.io/kubelet v0.19.0-rc.3
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200416193108-93ff8fc5e457/go.mod h1:CIpoNLW4Lm9zNVFRgqQIylnbZi/x9TnulTEA8edC0O4=
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200501183829-d8e5fa33347a h1:HA7wERlEdha8KL+Ezpajon+W/H+hOUVZB2QmQs57Co0=
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200501183829-d8e5fa33347a/go.mod h1:tgR06vqFs3qxPEkOFmiMDy9UbQ0PMcee/FK6dOyiHFs=
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200817205730-0b4482256ca1 h1:kjwCNY83bnHUNR6XijS2HDxuXqIp+W8lwk17N2RXlKI=
github.com/kubernetes-sigs/bootkube v0.14.1-0.20200817205730-0b4482256ca1/go.mod h1:tgR06vqFs3qxPEkOFmiMDy9UbQ0PMcee/FK6dOyiHFs=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
Expand Down Expand Up @@ -734,6 +734,8 @@ go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200716221620-18dfb9cca345 h1:2gOG36vt1BhUqpzxwZLZJxUim2dHB05vw+RAn4Q6YOU=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200716221620-18dfb9cca345/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
Expand Down Expand Up @@ -1087,7 +1089,10 @@ k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.19.0-rc.3 h1:awj/gA5ZC1m9H+iKT9EFxuiss1LNk6O2zVpDJXhr7uU=
k8s.io/apimachinery v0.19.0-rc.3/go.mod h1:oE8UQU9DqIIc9PyIEYxTj/oJECzZLymCEU9dL0H4F+o=
k8s.io/apiserver v0.18.2 h1:fwKxdTWwwYhxvtjo0UUfX+/fsitsNtfErPNegH2x9ic=
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
k8s.io/apiserver v0.19.0-rc.3 h1:uJ/yiqeFujHV+QOCoI9wFkIOXyvS9KEy5nSbosU4kXY=
k8s.io/apiserver v0.19.0-rc.3/go.mod h1:EqUGouRrGmm6pi77sXXGjRqylr+X08Kz87v2aBJQmkk=
k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
Expand Down Expand Up @@ -1130,6 +1135,7 @@ k8s.io/utils v0.0.0-20200720150651-0bdb4ca86cbc h1:GiXZzevctVRRBh56shqcqB9s9ReWM
k8s.io/utils v0.0.0-20200720150651-0bdb4ca86cbc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.0 h1:JI5bQQfabPDe8cSuK/lFMm2xB3faNkWyXyxT69FVmS8=
sigs.k8s.io/structured-merge-diff v1.0.0/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
Expand Down
4 changes: 0 additions & 4 deletions internal/app/bootkube/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ import (

// nolint: gocyclo
func generateAssets(config config.Provider) (err error) {
if err = os.MkdirAll(constants.ManifestsDirectory, 0o644); err != nil {
return err
}

// Ensure assets directory does not exist / is left over from a failed install
if err = os.RemoveAll(constants.AssetsDirectory); err != nil {
// Ignore if the directory does not exist
Expand Down
22 changes: 18 additions & 4 deletions internal/app/bootkube/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,28 @@ import (
)

var (
configPath *string
strict *bool
configPath *string
strict *bool
recover *bool
recoverSource *string
)

func init() {
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds | log.Ltime)

configPath = flag.String("config", "", "the path to the config")
strict = flag.Bool("strict", true, "require all manifests to cleanly apply")
recover = flag.Bool("recover", false, "run recovery instead of generate")
recoverSource = flag.String("recover-source", "ETCD", "recovery source to use")

flag.Parse()
}

func run() error {
if err := os.MkdirAll(constants.ManifestsDirectory, 0o644); err != nil {
return err
}

defaultRequiredPods := []string{
"kube-system/pod-checkpointer",
"kube-system/kube-apiserver",
Expand Down Expand Up @@ -100,8 +108,14 @@ func main() {
log.Fatalf("failed to create config from file: %v", err)
}

if err := generateAssets(config); err != nil {
log.Fatalf("error generating assets: %s", err)
if *recover {
if err := recoverAssets(config); err != nil {
log.Fatalf("error recovering assets: %s", err)
}
} else {
if err := generateAssets(config); err != nil {
log.Fatalf("error generating assets: %s", err)
}
}

if err := run(); err != nil {
Expand Down
112 changes: 112 additions & 0 deletions internal/app/bootkube/recover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
"bytes"
"context"
"crypto/aes"
"encoding/base64"
"errors"
"fmt"
"os"

"github.com/kubernetes-sigs/bootkube/pkg/recovery"
"go.etcd.io/etcd/clientv3"
k8saes "k8s.io/apiserver/pkg/storage/value/encrypt/aes"

"github.com/talos-systems/talos/internal/pkg/etcd"
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

//nolint: gocyclo
func recoverAssets(config config.Provider) error {
// Ensure assets directory does not exist / is left over from a failed install
if err := os.RemoveAll(constants.AssetsDirectory); err != nil {
// Ignore if the directory does not exist
if !errors.Is(err, os.ErrNotExist) {
return err
}
}

var (
backend recovery.Backend
err error
)

switch *recoverSource {
case machineapi.RecoverRequest_ETCD.String():
var client *clientv3.Client

client, err = etcd.NewClient([]string{"127.0.0.1:2379"})
if err != nil {
return err
}

var transform recovery.TransformerFromStorage

transform, err = aesTransformer(config.Cluster())
if err != nil {
return err
}

backend = recovery.NewEtcdBackendWithTransformer(client, "/registry", transform)
case machineapi.RecoverRequest_APISERVER.String():
backend, err = recovery.NewAPIServerBackend(constants.RecoveryKubeconfig)
if err != nil {
return err
}
}

as, err := recovery.Recover(context.Background(), backend, constants.RecoveryKubeconfig)
if err != nil {
return err
}

if err = os.MkdirAll(constants.AssetsDirectory, 0o600); err != nil {
return err
}

if err = as.WriteFiles(constants.AssetsDirectory); err != nil {
return fmt.Errorf("failed to write recovered assets: %w", err)
}

return nil
}

func aesTransformer(clusterConfig config.ClusterConfig) (recovery.TransformerFromStorage, error) {
key, err := base64.StdEncoding.DecodeString(clusterConfig.AESCBCEncryptionSecret())
if err != nil {
return nil, err
}

cipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

transformer := k8saes.NewCBCTransformer(cipher)

return func(value []byte) ([]byte, error) {
const (
aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:"
aesCBCKeyName = "key1:"

aesCBCPrefix = aesCBCTransformerPrefixV1 + aesCBCKeyName
)

if !bytes.HasPrefix(value, []byte(aesCBCPrefix)) {
return value, nil
}

value = value[len(aesCBCPrefix):]

value, _, e := transformer.TransformFromStorage(value, nil)

return value, e
}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import (
"golang.org/x/sys/unix"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"

"github.com/kubernetes-sigs/bootkube/pkg/recovery"

installer "github.com/talos-systems/talos/cmd/installer/pkg/install"
"github.com/talos-systems/talos/internal/app/machined/internal/install"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
Expand Down Expand Up @@ -1568,56 +1566,24 @@ func Recover(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc,
return runtime.ErrInvalidSequenceData
}

kubeconfigPath := "/etc/kubernetes/recovery.yaml"

var b bytes.Buffer

if err = kubeconfig.GenerateAdmin(r.Config().Cluster(), &b); err != nil {
return err
}

if err = ioutil.WriteFile(kubeconfigPath, b.Bytes(), 0o600); err != nil {
if err = ioutil.WriteFile(constants.RecoveryKubeconfig, b.Bytes(), 0o600); err != nil {
return fmt.Errorf("failed to create recovery kubeconfig: %w", err)
}

// nolint: errcheck
defer os.Remove(kubeconfigPath)

var backend recovery.Backend

switch in.Source {
case machineapi.RecoverRequest_ETCD:
var client *clientv3.Client

client, err = etcd.NewClient([]string{"127.0.0.1:2379"})
if err != nil {
return err
}

backend = recovery.NewEtcdBackend(client, "/registry")
defer os.Remove(constants.RecoveryKubeconfig)

case machineapi.RecoverRequest_APISERVER:
backend, err = recovery.NewAPIServerBackend(kubeconfigPath)
if err != nil {
return err
}
svc := &services.Bootkube{
Recover: true,
Source: in.Source,
}

as, err := recovery.Recover(context.Background(), backend, kubeconfigPath)
if err != nil {
return err
}

if err = os.MkdirAll(constants.AssetsDirectory, 0o600); err != nil {
return err
}

if err = as.WriteFiles(constants.AssetsDirectory); err != nil {
return fmt.Errorf("failed to write recovered assets: %w", err)
}

svc := &services.Bootkube{Recover: true}

// unload bootkube (if any instance ran before)
if err = system.Services(r).Unload(ctx, svc.ID(r)); err != nil {
return err
Expand All @@ -1629,7 +1595,10 @@ func Recover(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc,
return fmt.Errorf("failed to start bootkube: %w", err)
}

return nil
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()

return system.WaitForService(system.StateEventFinished, svc.ID(r)).Wait(ctx)
}, "recover"
}

Expand Down
4 changes: 4 additions & 0 deletions internal/app/machined/pkg/system/services/bootkube.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner/containerd"
"github.com/talos-systems/talos/internal/pkg/etcd"
"github.com/talos-systems/talos/pkg/conditions"
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/retry"
Expand All @@ -32,6 +33,7 @@ import (
// Bootkube implements the Service interface. It serves as the concrete type with
// the required methods.
type Bootkube struct {
Source machineapi.RecoverRequest_Source
Recover bool

provisioned bool
Expand Down Expand Up @@ -163,6 +165,8 @@ func (b *Bootkube) Runner(r runtime.Runtime) (runner.Runner, error) {
"/bootkube",
"--config=" + constants.ConfigPath,
"--strict=" + strconv.FormatBool(!b.Recover),
"--recover=" + strconv.FormatBool(b.Recover),
"--recover-source=" + b.Source.String(),
},
}

Expand Down

0 comments on commit 6a7cc02

Please sign in to comment.