talos/internal/integration/cli/gen.go
Utku Ozdemir a75fe7600d
feat: gen secrets from kubernetes pki dir
This PR allows the ability to generate `secrets.yaml` (`talosctl gen secrets`) using a Kubernetes PKI directory path (e.g. `/etc/kubernetes/pki`) as input. Also introduces the flag `--kubernetes-bootstrap-token` to be able to set a static Kubernetes bootstrap token to the generated `secrets.yaml` file instead of a randomly-generated one. Closes siderolabs/talos#5894.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2022-07-16 13:06:32 +02:00

286 lines
8.1 KiB
Go

// 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/.
//go:build integration_cli
// +build integration_cli
package cli
import (
"encoding/json"
"os"
"regexp"
"gopkg.in/yaml.v3"
"github.com/talos-systems/talos/internal/integration/base"
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
)
// GenSuite verifies dmesg command.
type GenSuite struct {
base.CLISuite
tmpDir string
savedCwd string
}
// SuiteName ...
func (suite *GenSuite) SuiteName() string {
return "cli.GenSuite"
}
// SetupTest ...
func (suite *GenSuite) SetupTest() {
suite.tmpDir = suite.T().TempDir()
var err error
suite.savedCwd, err = os.Getwd()
suite.Require().NoError(err)
suite.Require().NoError(os.Chdir(suite.tmpDir))
}
// TearDownTest ...
func (suite *GenSuite) TearDownTest() {
if suite.savedCwd != "" {
suite.Require().NoError(os.Chdir(suite.savedCwd))
}
}
// TestCA ...
func (suite *GenSuite) TestCA() {
suite.RunCLI([]string{"gen", "ca", "--organization", "Foo"},
base.StdoutEmpty())
suite.Assert().FileExists("Foo.crt")
suite.Assert().FileExists("Foo.sha256")
suite.Assert().FileExists("Foo.key")
}
// TestKey ...
func (suite *GenSuite) TestKey() {
suite.RunCLI([]string{"gen", "key", "--name", "Foo"},
base.StdoutEmpty())
suite.Assert().FileExists("Foo.key")
}
// TestCSR ...
func (suite *GenSuite) TestCSR() {
suite.RunCLI([]string{"gen", "key", "--name", "Foo"},
base.StdoutEmpty())
suite.RunCLI([]string{"gen", "csr", "--key", "Foo.key", "--ip", "10.0.0.1"},
base.StdoutEmpty())
suite.Assert().FileExists("Foo.csr")
}
// TestCrt ...
func (suite *GenSuite) TestCrt() {
suite.RunCLI([]string{"gen", "ca", "--organization", "Foo"},
base.StdoutEmpty())
suite.RunCLI([]string{"gen", "key", "--name", "Bar"},
base.StdoutEmpty())
suite.RunCLI([]string{"gen", "csr", "--key", "Bar.key", "--ip", "10.0.0.1"},
base.StdoutEmpty())
suite.RunCLI([]string{"gen", "crt", "--ca", "Foo", "--csr", "Bar.csr", "--name", "foobar"},
base.StdoutEmpty())
suite.Assert().FileExists("foobar.crt")
}
// TestKeypair ...
func (suite *GenSuite) TestKeypair() {
suite.RunCLI([]string{"gen", "keypair", "--organization", "Foo", "--ip", "10.0.0.1"},
base.StdoutEmpty())
suite.Assert().FileExists("Foo.crt")
suite.Assert().FileExists("Foo.key")
}
// TestGenConfigURLValidation ...
func (suite *GenSuite) TestGenConfigURLValidation() {
suite.RunCLI([]string{"gen", "config", "foo", "192.168.0.1"},
base.ShouldFail(),
base.StdoutEmpty(),
base.StderrShouldMatch(regexp.MustCompile(`\Qtry: "https://192.168.0.1:6443"`)))
suite.RunCLI([]string{"gen", "config", "foo", "192.168.0.1:6443"},
base.ShouldFail(),
base.StdoutEmpty(),
base.StderrShouldMatch(regexp.MustCompile(`\Qtry: "https://192.168.0.1:6443"`)))
suite.RunCLI([]string{"gen", "config", "foo", "192.168.0.1:2000"},
base.ShouldFail(),
base.StdoutEmpty(),
base.StderrShouldMatch(regexp.MustCompile(`\Qtry: "https://192.168.0.1:2000"`)))
suite.RunCLI([]string{"gen", "config", "foo", "http://192.168.0.1:2000"},
base.ShouldFail(),
base.StdoutEmpty(),
base.StderrShouldMatch(regexp.MustCompile(`\Qtry: "https://192.168.0.1:2000"`)))
}
// TestGenConfigPatchJSON6902 verifies that gen config --config-patch works with JSON patches.
func (suite *GenSuite) TestGenConfigPatchJSON6902() {
patch, err := json.Marshal([]map[string]interface{}{
{
"op": "replace",
"path": "/cluster/clusterName",
"value": "bar",
},
})
suite.Assert().NoError(err)
suite.testGenConfigPatch(patch)
}
// TestGenConfigPatchStrategic verifies that gen config --config-patch works with strategic merge patches.
func (suite *GenSuite) TestGenConfigPatchStrategic() {
patch, err := yaml.Marshal(map[string]interface{}{
"cluster": map[string]interface{}{
"clusterName": "bar",
},
})
suite.Assert().NoError(err)
suite.testGenConfigPatch(patch)
}
func (suite *GenSuite) testGenConfigPatch(patch []byte) {
for _, tt := range []struct {
flag string
shouldAffect map[string]bool
}{
{
flag: "config-patch",
shouldAffect: map[string]bool{
"controlplane.yaml": true,
"worker.yaml": true,
},
},
{
flag: "config-patch-control-plane",
shouldAffect: map[string]bool{
"controlplane.yaml": true,
},
},
{
flag: "config-patch-worker",
shouldAffect: map[string]bool{
"worker.yaml": true,
},
},
} {
tt := tt
suite.Run(tt.flag, func() {
suite.RunCLI([]string{"gen", "config", "foo", "https://192.168.0.1:6443", "--" + tt.flag, string(patch)})
for _, configName := range []string{"controlplane.yaml", "worker.yaml"} {
cfg, err := configloader.NewFromFile(configName)
suite.Require().NoError(err)
switch {
case tt.shouldAffect[configName]:
suite.Assert().Equal("bar", cfg.Cluster().Name(), "checking %q", configName)
case configName == "worker.yaml":
suite.Assert().Equal("", cfg.Cluster().Name(), "checking %q", configName)
default:
suite.Assert().Equal("foo", cfg.Cluster().Name(), "checking %q", configName)
}
}
})
}
}
// TestSecrets ...
func (suite *GenSuite) TestSecrets() {
suite.RunCLI([]string{"gen", "secrets"}, base.StdoutEmpty())
suite.Assert().FileExists("secrets.yaml")
defer os.Remove("secrets.yaml") // nolint:errcheck
suite.RunCLI([]string{"gen", "secrets", "--output-file", "/tmp/secrets2.yaml"}, base.StdoutEmpty())
suite.Assert().FileExists("/tmp/secrets2.yaml")
defer os.Remove("/tmp/secrets2.yaml") // nolint:errcheck
suite.RunCLI([]string{"gen", "secrets", "-o", "secrets3.yaml", "--talos-version", "v0.8"}, base.StdoutEmpty())
suite.Assert().FileExists("secrets3.yaml")
defer os.Remove("secrets3.yaml") // nolint:errcheck
}
// TestSecretsWithPKIDirAndToken ...
func (suite *GenSuite) TestSecretsWithPKIDirAndToken() {
path := "/tmp/secrets-with-pki-dir-and-token.yaml"
tempDir := suite.T().TempDir()
dir, err := writeKubernetesPKIFiles(tempDir)
suite.Assert().NoError(err)
defer os.RemoveAll(dir) //nolint:errcheck
suite.RunCLI([]string{
"gen", "secrets", "--from-kubernetes-pki", dir,
"--kubernetes-bootstrap-token", "test-token",
"--output-file", path,
}, base.StdoutEmpty())
suite.Assert().FileExists(path)
defer os.Remove(path) //nolint:errcheck
secretsYaml, err := os.ReadFile(path)
suite.Assert().NoError(err)
var secrets generate.SecretsBundle
err = yaml.Unmarshal(secretsYaml, &secrets)
suite.Assert().NoError(err)
suite.Assert().Equal("test-token", secrets.Secrets.BootstrapToken, "bootstrap token does not match")
suite.Assert().Equal(pkiCACrt, secrets.Certs.K8s.Crt, "k8s ca cert does not match")
suite.Assert().Equal(pkiCAKey, secrets.Certs.K8s.Key, "k8s ca key does not match")
suite.Assert().Equal(pkiFrontProxyCACrt, secrets.Certs.K8sAggregator.Crt, "k8s aggregator ca cert does not match")
suite.Assert().Equal(pkiFrontProxyCAKey, secrets.Certs.K8sAggregator.Key, "k8s aggregator ca key does not match")
suite.Assert().Equal(pkiSAKey, secrets.Certs.K8sServiceAccount.Key, "k8s service account key does not match")
suite.Assert().Equal(pkiEtcdCACrt, secrets.Certs.Etcd.Crt, "etcd ca cert does not match")
suite.Assert().Equal(pkiEtcdCAKey, secrets.Certs.Etcd.Key, "etcd ca key does not match")
}
// TestConfigWithSecrets tests the gen config command with secrets provided.
func (suite *GenSuite) TestConfigWithSecrets() {
suite.RunCLI([]string{"gen", "secrets"}, base.StdoutEmpty())
suite.Assert().FileExists("secrets.yaml")
secretsYaml, err := os.ReadFile("secrets.yaml")
suite.Assert().NoError(err)
suite.RunCLI([]string{"gen", "config", "foo", "https://192.168.0.1:6443", "--with-secrets", "secrets.yaml"})
config, err := configloader.NewFromFile("controlplane.yaml")
suite.Assert().NoError(err)
configSecretsBundle := generate.NewSecretsBundleFromConfig(generate.NewClock(), config)
configSecretsBundleBytes, err := yaml.Marshal(configSecretsBundle)
suite.Assert().NoError(err)
suite.Assert().YAMLEq(string(secretsYaml), string(configSecretsBundleBytes))
}
func init() {
allSuites = append(allSuites, new(GenSuite))
}