diff --git a/pkg/cli/cmds/server.go b/pkg/cli/cmds/server.go index 138d130297..c51b9bf928 100644 --- a/pkg/cli/cmds/server.go +++ b/pkg/cli/cmds/server.go @@ -42,6 +42,7 @@ type Server struct { DisableNPC bool ClusterInit bool ClusterReset bool + EncryptSecrets bool } var ServerConfig Server @@ -262,6 +263,11 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { EnvVar: "K3S_CLUSTER_RESET", Destination: &ServerConfig.ClusterReset, }, + cli.BoolFlag{ + Name: "secrets-encryption", + Usage: "(experimental) Enable Secret encryption at rest", + Destination: &ServerConfig.EncryptSecrets, + }, // Hidden/Deprecated flags below diff --git a/pkg/cli/server/server.go b/pkg/cli/server/server.go index dbf27eb298..1758ad7b3e 100644 --- a/pkg/cli/server/server.go +++ b/pkg/cli/server/server.go @@ -100,6 +100,7 @@ func run(app *cli.Context, cfg *cmds.Server) error { serverConfig.ControlConfig.DisableNPC = cfg.DisableNPC serverConfig.ControlConfig.ClusterInit = cfg.ClusterInit serverConfig.ControlConfig.ClusterReset = cfg.ClusterReset + serverConfig.ControlConfig.EncryptSecrets = cfg.EncryptSecrets if cmds.AgentConfig.FlannelIface != "" && cmds.AgentConfig.NodeIP == "" { cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface) diff --git a/pkg/daemons/config/types.go b/pkg/daemons/config/types.go index d1f23fc9d1..21179d177d 100644 --- a/pkg/daemons/config/types.go +++ b/pkg/daemons/config/types.go @@ -111,6 +111,7 @@ type Control struct { DisableNPC bool ClusterInit bool ClusterReset bool + EncryptSecrets bool BindAddress string SANs []string @@ -128,6 +129,7 @@ type ControlRuntimeBootstrap struct { RequestHeaderCA string RequestHeaderCAKey string IPSECKey string + EncryptionConfig string } type ControlRuntime struct { diff --git a/pkg/daemons/control/server.go b/pkg/daemons/control/server.go index 62d3b1fe94..c1bdc1705a 100644 --- a/pkg/daemons/control/server.go +++ b/pkg/daemons/control/server.go @@ -3,9 +3,11 @@ package control import ( "context" "crypto" + cryptorand "crypto/rand" "crypto/x509" + b64 "encoding/base64" + "encoding/json" "fmt" - "html/template" "io/ioutil" "math/rand" "net" @@ -15,6 +17,7 @@ import ( "path/filepath" "strconv" "strings" + "text/template" "time" // registering k3s cloud provider @@ -29,6 +32,8 @@ import ( "github.com/rancher/wrangler-api/pkg/generated/controllers/rbac" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + apiserverconfigv1 "k8s.io/apiserver/pkg/apis/config/v1" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -70,8 +75,9 @@ users: ) const ( - userTokenSize = 16 + userTokenSize = 8 ipsecTokenSize = 48 + aescbcKeySize = 32 ) func Server(ctx context.Context, cfg *config.Control) error { @@ -201,7 +207,9 @@ func apiServer(ctx context.Context, cfg *config.Control, runtime *config.Control argsMap["client-ca-file"] = runtime.ClientCA argsMap["enable-admission-plugins"] = "NodeRestriction" argsMap["anonymous-auth"] = "false" - + if cfg.EncryptSecrets { + argsMap["encryption-provider-config"] = runtime.EncryptionConfig + } args := config.GetArgsList(argsMap, cfg.ExtraAPIArgs) command := app.NewAPIServerCommand(ctx.Done()) @@ -308,6 +316,10 @@ func prepare(ctx context.Context, config *config.Control, runtime *config.Contro runtime.ClientAuthProxyCert = path.Join(config.DataDir, "tls", "client-auth-proxy.crt") runtime.ClientAuthProxyKey = path.Join(config.DataDir, "tls", "client-auth-proxy.key") + if config.EncryptSecrets { + runtime.EncryptionConfig = path.Join(config.DataDir, "cred", "encryption-config.json") + } + cluster := cluster.New(config) if err := cluster.Join(ctx); err != nil { @@ -330,6 +342,10 @@ func prepare(ctx context.Context, config *config.Control, runtime *config.Contro return err } + if err := genEncryptionConfig(config, runtime); err != nil { + return err + } + if err := readTokens(runtime); err != nil { return err } @@ -861,3 +877,51 @@ func promise(f func() error) <-chan error { }() return c } + +func genEncryptionConfig(controlConfig *config.Control, runtime *config.ControlRuntime) error { + if !controlConfig.EncryptSecrets { + return nil + } + if s, err := os.Stat(runtime.EncryptionConfig); err == nil && s.Size() > 0 { + return nil + } + + aescbcKey := make([]byte, aescbcKeySize, aescbcKeySize) + _, err := cryptorand.Read(aescbcKey) + if err != nil { + return err + } + encodedKey := b64.StdEncoding.EncodeToString(aescbcKey) + + encConfig := apiserverconfigv1.EncryptionConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "EncryptionConfiguration", + APIVersion: "apiserver.config.k8s.io/v1", + }, + Resources: []apiserverconfigv1.ResourceConfiguration{ + { + Resources: []string{"secrets"}, + Providers: []apiserverconfigv1.ProviderConfiguration{ + { + AESCBC: &apiserverconfigv1.AESConfiguration{ + Keys: []apiserverconfigv1.Key{ + { + Name: "aescbckey", + Secret: encodedKey, + }, + }, + }, + }, + { + Identity: &apiserverconfigv1.IdentityConfiguration{}, + }, + }, + }, + }, + } + jsonfile, err := json.Marshal(encConfig) + if err != nil { + return err + } + return ioutil.WriteFile(runtime.EncryptionConfig, jsonfile, 0600) +}