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>
This commit is contained in:
Utku Ozdemir 2022-07-12 14:24:32 +02:00
parent a1d7b535ad
commit a75fe7600d
No known key found for this signature in database
GPG Key ID: 65933E76F0549B0D
14 changed files with 553 additions and 89 deletions

View File

@ -16,8 +16,10 @@ import (
)
var genSecretsCmdFlags struct {
outputFile string
talosVersion string
outputFile string
talosVersion string
fromKubernetesPki string
kubernetesBootstrapToken string
}
// genSecretsCmd represents the `gen secrets` command.
@ -27,18 +29,32 @@ var genSecretsCmd = &cobra.Command{
Long: ``,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
genOptions := make([]generate.GenOption, 0, 1)
var (
secretsBundle *generate.SecretsBundle
versionContract *config.VersionContract
err error
)
if genSecretsCmdFlags.talosVersion != "" {
versionContract, err := config.ParseContractFromVersion(genSecretsCmdFlags.talosVersion)
versionContract, err = config.ParseContractFromVersion(genSecretsCmdFlags.talosVersion)
if err != nil {
return fmt.Errorf("invalid talos-version: %w", err)
}
genOptions = append(genOptions, generate.WithVersionContract(versionContract))
}
secretsBundle, err := generate.NewSecretsBundle(generate.NewClock(), genOptions...)
if genSecretsCmdFlags.fromKubernetesPki != "" {
secretsBundle, err = generate.NewSecretsBundleFromKubernetesPKI(genSecretsCmdFlags.fromKubernetesPki,
genSecretsCmdFlags.kubernetesBootstrapToken, versionContract)
if err != nil {
return fmt.Errorf("failed to create secrets bundle: %w", err)
}
return writeSecretsBundleToFile(secretsBundle)
}
secretsBundle, err = generate.NewSecretsBundle(generate.NewClock(),
generate.WithVersionContract(versionContract),
)
if err != nil {
return fmt.Errorf("failed to create secrets bundle: %w", err)
}
@ -59,6 +75,8 @@ func writeSecretsBundleToFile(bundle *generate.SecretsBundle) error {
func init() {
genSecretsCmd.Flags().StringVarP(&genSecretsCmdFlags.outputFile, "output-file", "o", "secrets.yaml", "path of the output file")
genSecretsCmd.Flags().StringVar(&genSecretsCmdFlags.talosVersion, "talos-version", "", "the desired Talos version to generate secrets bundle for (backwards compatibility, e.g. v0.8)")
genSecretsCmd.Flags().StringVarP(&genSecretsCmdFlags.fromKubernetesPki, "from-kubernetes-pki", "p", "", "use a Kubernetes PKI directory (e.g. /etc/kubernetes/pki) as input")
genSecretsCmd.Flags().StringVarP(&genSecretsCmdFlags.kubernetesBootstrapToken, "kubernetes-bootstrap-token", "t", "", "use the provided bootstrap token as input")
Cmd.AddCommand(genSecretsCmd)
}

View File

@ -79,6 +79,23 @@ machine:
```
Patch format is detected automatically.
"""
[notes.gen-secrets-from-pki]
title = "Generating Talos secrets from PKI directory"
description="""\
It is now possible to generate a secrets bundle from a Kubernetes PKI directory (e.g. `/etc/kubernetes/pki`).
You can also specify a bootstrap token to be used in the secrets bundle.
This secrets bundle can then be used to generate a machine config.
This facilitates migrating clusters (e.g. created using `kubeadm`) to Talos.
```
talosctl gen secrets --kubernetes-bootstrap-token znzio1.1ifu15frz7jd59pv --from-kubernetes-pki /etc/kubernetes/pki
talosctl gen config --with-secrets secrets.yaml my-cluster https://172.20.0.1:6443
```
"""
[make_deps]

View File

@ -9,7 +9,6 @@ package cli
import (
"encoding/json"
"io/ioutil"
"os"
"regexp"
@ -209,11 +208,56 @@ 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.
@ -221,7 +265,7 @@ func (suite *GenSuite) TestConfigWithSecrets() {
suite.RunCLI([]string{"gen", "secrets"}, base.StdoutEmpty())
suite.Assert().FileExists("secrets.yaml")
secretsYaml, err := ioutil.ReadFile("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"})

View File

@ -0,0 +1,67 @@
// 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 cli
import (
_ "embed"
"os"
"path/filepath"
)
var (
//go:embed "testdata/pki/ca.crt"
pkiCACrt []byte
//go:embed "testdata/pki/ca.key"
pkiCAKey []byte
//go:embed "testdata/pki/front-proxy-ca.crt"
pkiFrontProxyCACrt []byte
//go:embed "testdata/pki/front-proxy-ca.key"
pkiFrontProxyCAKey []byte
//go:embed "testdata/pki/sa.key"
pkiSAKey []byte
//go:embed "testdata/pki/etcd/ca.crt"
pkiEtcdCACrt []byte
//go:embed "testdata/pki/etcd/ca.key"
pkiEtcdCAKey []byte
)
func writeKubernetesPKIFiles(dir string) (string, error) {
var err error
if err = os.WriteFile(filepath.Join(dir, "ca.crt"), pkiCACrt, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(dir, "ca.key"), pkiCAKey, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(dir, "front-proxy-ca.crt"), pkiFrontProxyCACrt, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(dir, "front-proxy-ca.key"), pkiFrontProxyCAKey, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(dir, "sa.key"), pkiSAKey, 0o777); err != nil {
return "", err
}
etcdDir := filepath.Join(dir, "etcd")
if err = os.Mkdir(etcdDir, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(etcdDir, "ca.crt"), pkiEtcdCACrt, 0o777); err != nil {
return "", err
}
if err = os.WriteFile(filepath.Join(etcdDir, "ca.key"), pkiEtcdCAKey, 0o777); err != nil {
return "", err
}
return dir, nil
}

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIyMDcwODExMTM0OFoXDTMyMDcwNTExMTM0OFowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGI
T8ncEbVJ7LtRTZY6Vc2bXlMtagzCqpP+29H7HtsgV4T64QJsPRnfCh0PzOit7JJq
SRj526wHZRfSvu0M9wZ2KaC4MVDMP2KhBUKW63nUmdXQMf+z1gHKzCMloAMa0Avb
DVsoc9NaiiHX8m59gX328xEHQjNxnNGIretBjsjZw/Xeo2BflVXahnnxMVJqnzEa
0jpnGh5BaO4aPKDrJbyELz8Y8F+NGJ2zkSVtBh0gYZrejnioEiSFkSvcxDw3Xhg2
QL+NUsRrhYHq10apJhuSkPkraCogbHlN33dslJ+I85xszKt437gGkpDoTKXaxe9L
f9xB40PgToK9QfAOIFMCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFF06xQ3JTko0LcX5pvAdp+mLFWgMMBUGA1UdEQQO
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAIkhsj6yVvEoN4q7nj97
vY0RpAOyysmhigHK0miioKsd94GDb+aMBYFLKliU48B5/n/KXblu7xsTane8uB3C
VeBywkDXLN2a9ax4BaxIkleDOX1xZN4BtxIfdU1QGhFQU0JPDCMxbDjbfN2Kg2Wi
iESrsXYKDq2pLNeQdszxPGNlAOjssVHY6IivWOcMRHP0yCDTl5ooq180+U7smFdz
NM/6udMOhsgh6bUCeMu9mhaPXMBmK0Lcd68PFunAA8q7a5OfTgIhGC9n7Q0L6CMw
7yXb97bd9bPZqeiuw7G7+UiNkJrBdIMc0AYE+wG44Uxu9usrGZZt6zLYzfnJ2vRZ
qac=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsYhPydwRtUnsu1FNljpVzZteUy1qDMKqk/7b0fse2yBXhPrh
Amw9Gd8KHQ/M6K3skmpJGPnbrAdlF9K+7Qz3BnYpoLgxUMw/YqEFQpbredSZ1dAx
/7PWAcrMIyWgAxrQC9sNWyhz01qKIdfybn2BffbzEQdCM3Gc0Yit60GOyNnD9d6j
YF+VVdqGefExUmqfMRrSOmcaHkFo7ho8oOslvIQvPxjwX40YnbORJW0GHSBhmt6O
eKgSJIWRK9zEPDdeGDZAv41SxGuFgerXRqkmG5KQ+StoKiBseU3fd2yUn4jznGzM
q3jfuAaSkOhMpdrF70t/3EHjQ+BOgr1B8A4gUwIDAQABAoIBAQCIvqY2pfw916Mw
5X8NqAFPTc1p5CE7kvYw6K4JH5S01ESVeWi3pQerVdFEcVc0IkOGw7dqNYqvB0Mn
Bn1pugLMR1fpI/dYdPqdzclvcTAPt2KG/saEXtEIsFxs9h46RfzaJPA0twQAWEzt
pJhn4uRLUlwHUb/8QBa6jrzn6KdCrLC0jc0g8c77x3omkfy0chug7q2s/yX2MwEX
p5pbyPiGZkNIjuDeic3D0ssrH7v7gEYBdybgl78LrLkKA8gEFLrGvrT5nuLYhxkF
dhRu3+p9mhhKmJQndto8Y4WeYhXZ7qN2YZbfcv1Xni6cq4UhLmr2czRaK0KVZ15H
kzYh0I8BAoGBAOTjpoVVc+IgVJnGeglrJP8zOKEiwj3R+eF9DiQuTdnop0SBiqYB
JT6/b/apdYEGz2nBs3M1k6CmNrkzGADMNins7m1MRHkljRqu1IHSF3Us2yifPUef
lEUHtXf3ANvf3YJWuGO7RT4sird/1vXE/0TCzjWEvkHc6Zvky/DCydTJAoGBAMaP
bWsiDSFpLV6iCiq18bmJJoUDnkXqnwsUVw9C978h2Hx5ysYfOUt6S6Q7UsR7hKW5
iay1aiFPBjLQyD4Ac8feZFQEQ5gFYXPEfIV3OYZDz2U+gAZp33ow7/H/73kngelV
6EXhTYgDRUwfTBOMSlzhCZwQb8Rzpv+gC3TFsmY7AoGAWewB2LIYo8bV1c/+08Jv
N39VCSERtJ3QkMDDlH1Igop/ZE+MO+mJS1yETSCIFFerlr3NlT6AMAX8y8eB75ZK
1S/K/8+NuxaAl/IFdLcoFhW4R/4/YesUogYESgwVH0yUxobxS+Ufr+xp1ut3dPie
3NG3l5j98fwrHt7FLGIqTtkCgYEAjezfDQCd2g/PuiCgm77JNRDvU4wuiVMWs1iq
keIQK7IJh4+WfN68mVKk1pMAqiiPu9VOrwBNB9nwWEoblxXDrE0t8U/K8NKHwbPk
PZHmsC2wBHIUGIF8l157Y8LIbRTsKtiY2bodLOcJlUuZmS9hx9migMbO3OC9sWG4
TpMw3RkCgYA8ssuhj5wrSQabCXbOU0pn/qbePHWfvh+DcRUXnTtRoothpqvDQxuS
73j2NB42UG7j0eWRK993ONGTJ/QNMAJxXwLEuLFtoKHdh7pkq3uFolyNzR+T0vna
awkSRmJsbuV7C4mDVHgEaqcEzv/VyeAokw/hqB1Ga9fvauj6MDPshw==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9TCCAd2gAwIBAgIBADANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdldGNk
LWNhMB4XDTIyMDcwODExMTM0OFoXDTMyMDcwNTExMTM0OFowEjEQMA4GA1UEAxMH
ZXRjZC1jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOgw+tA7odeO
H5gKPlhWBjijuNoLlMCJSkGG0Ca/fI6Bk2Y+fzuVv8NWsRrhML3dEWJgXccMhoG0
v9MatmvF5nYfm802ZaDCsA6RdJG3PygRTw69pmC4ETfv0+YXpFFfFBeWWb9MsXIP
ULooepYy4bWvjG7ZDCeFQ8ACHGE17U0O5rFBqiY0okBjSkl1/oHSzlAVCxa4dOaK
/Vj8A8CefT18JZ+jzAbgN7jRm1GWzYTWqACVa5CnRDYRglrZr9DLvaj0ASqck5DD
zdJYVQJeVqS7L8qghXfbXzxlkKesRtQsj7V2trpDBuDASJPk6ZmwEWlaI0Wdc3eK
nYHgaWybLykCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFKN5e0IuTpOoTqWB9+MQp/k/OgXgMBIGA1UdEQQLMAmCB2V0
Y2QtY2EwDQYJKoZIhvcNAQELBQADggEBAGmGUUqeGe4RXd3kZGTRcdJMo7OkuEK5
hH/FHfrLLc3VNoWQafdq4oVrF9bwqDUPRuPgrXl+QY+s4/ztDyHmuKGdLWIT2dCE
0Ztnpe17VoMrkJxmFvKEWrT74EnT0QIeFs+lf8+SJWTLYKBRpGrsQKq84uds++gV
BPLuu8Z0e5vvkAFnX8m65SqyZwKfs3HuzaAnA57VGSJHCBrnKojsP8JdlWeQiStG
CtmzePeLqjVJnpNF/n1ST5Ewr4Kq/Cvf707gzs+spn0bt97QyNke6b24ZrkpUNu6
T/0bImLeYiGmSanRF3hAdN7IV0ah7tYOpmwk560kyF8tmd8jcKFgEd8=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6DD60Duh144fmAo+WFYGOKO42guUwIlKQYbQJr98joGTZj5/
O5W/w1axGuEwvd0RYmBdxwyGgbS/0xq2a8Xmdh+bzTZloMKwDpF0kbc/KBFPDr2m
YLgRN+/T5hekUV8UF5ZZv0yxcg9Quih6ljLhta+MbtkMJ4VDwAIcYTXtTQ7msUGq
JjSiQGNKSXX+gdLOUBULFrh05or9WPwDwJ59PXwln6PMBuA3uNGbUZbNhNaoAJVr
kKdENhGCWtmv0Mu9qPQBKpyTkMPN0lhVAl5WpLsvyqCFd9tfPGWQp6xG1CyPtXa2
ukMG4MBIk+TpmbARaVojRZ1zd4qdgeBpbJsvKQIDAQABAoIBAHI8xun0rOfU8Q5o
28uyZ1UumCAPWpxv76zVm0u1Ip8qeU7wqMC0KKj+2hwTd1uyjH8OUpVAQF1IhKhk
mCPmNkEfxBPvE4lIwD4qqmOW+OfJvE/QVy924GHZCTRHpXyzfrssKfPI0/T+PAWb
LNUBK7OsLzfKagR3uKGbaEMbuSkTn5zeCJ1vrFAuCaLmeauBsnoV7Z9AlbG6t8sp
hyD0O+pDPGuc+J99suGiysndwjczqntIKXcdgba2XxRE0QHM+ZYDPpyvCggKfoUH
sQ9Vz/UHpqTv8pbrTSZK59E8+16YfFyFC+iwTnTzoBAkO6NIivDTQvLcipweZEzt
5146EEECgYEA6O+Pd1tdkTVHLgSkKrkBo4p3g2iBFl626g7vlZGvDjVNNPBWwk5f
j3PbMKPfUfr96YB86lbetDzYdiB5LrLW5xG2GaSRziGI1/Tgdp6aR142TNljiEu7
dv4v3eAR5VfiA5CGera+k1VzKLIygfCDkUWWdG17LvVdHFtjN6XVU2UCgYEA/y6M
hZbZqCX9cx58rhJ38YvH9fdWSzw1r9crgM40uPHE4SHvin6/lR/rbNU4Geg/b4bn
FWnffCem8ov2tXcFe/ZBM8ZWrsRGDinkTh8dReh8ujE90YOqf3YSRYgUvSx8ITUn
zqhe43sGVAelKeVKPW/3Ok7RJXnRRsDIbSmfqnUCgYB7sDmON4XHxXK2jOBfjz2/
iZdMwAFLz59xSd0Onv1FnigRJE3tf5BerDaH7Xx4G78YbpHmHZrEOkr27udqVKyo
pk777tc9jbEMe4t1cWKa4vwScpzXkt9IoFDqkEDwd2ocWnIOV1t7ALTVt0n6laxH
R5xM1pXCqad3l09oDTbpwQKBgDlHwqVOCkeTV4QayNPuM1xWCymsPoOe3VI+U3aT
UwRcyNvcWT/WWbzosFj6t6AhIPQw7PhCjrb406HIRzXOpL2Btnsfv191kWAmiSf8
Ff8WQ8ErwnugOYpo/4r6E+Wu8aImo2vhIYOgnvgHy0xPOs31ryI4hPwLjy15osPW
Pw/tAoGBAKaT53bqIl+wzltM2ROtH9ppA98prDPM0GyZMhFed+XMLAkUX3qmFtj5
k51bhaq7ER4yByHX17Q7y3SoJ5NpccmFUhWhttRf/IraWEKsHXW6OY22wvtZN9Lx
Eh+rpZsjcozUCaoQWkOf20UpBSltIV5U4GmSpthnj0+zaRrorxWx
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIBADANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDEw5mcm9u
dC1wcm94eS1jYTAeFw0yMjA3MDgxMTEzNDhaFw0zMjA3MDUxMTEzNDhaMBkxFzAV
BgNVBAMTDmZyb250LXByb3h5LWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA4thL1utf/iXxylt24O3o3IV7oif5tiYR4SN9YnVb/GLyVtVdHuzPpigw
j+S7aw3mldNHVWbIPq9GncvVsgvlJYHu53WRzVfV7tAylt/ssSqHFv/p1e6rC2m9
hETHUZjSs8v0APWi8pfh15lJPqLS1lGF3QPVCPdc1t+8dBVnx8wnTgGVE7FVLjmc
3qIcXTR1TfcB4+X1ZnY5FNK7kR/kgIS64uZqntyNSW5W0SCFgWQClwwkzVpo4v/Y
sCHiWI7cIuXHTTd7ntHJab1Og1jAPNEmENvEtza1SqGTSEUQs5wjS+p+ii5E09b3
Nufj1e8hJaodTxr+24hoFcYxWIgxhwIDAQABo10wWzAOBgNVHQ8BAf8EBAMCAqQw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvw91sUxAgIX7zdqTKwJ0Sdxa9TUw
GQYDVR0RBBIwEIIOZnJvbnQtcHJveHktY2EwDQYJKoZIhvcNAQELBQADggEBAGHW
snsEJkN3d2vNJuFu6IVKurEZM8UbwHebJ5fQUHpCOKpFqSMdngF8rdcYNmHeUFMv
Qf9Fkm9nwKOhO9hApsQad7UgJ98YCzE+heTye7nKjrga+2lsJN/T3SgJGiAEkVjP
rq2SQ4MLCp56PyFI5CL9zqtjuXCI8Uhqqfru6tJEA75GA6VJZmfiOgzFQum7DeC2
9dNcvQb8p91LIl5tjvuTgPbJDjF1YX4n0iwoiA05e+rPrsPhyySQnJwFgTi+hqPZ
cBUPwVDYeiHseRbj3ODOoCv6PO4koO5m+tiFeUXJTynMbCUkyJqZ11TAYC6snHxi
mg+fblveomK3F3hLFdk=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4thL1utf/iXxylt24O3o3IV7oif5tiYR4SN9YnVb/GLyVtVd
HuzPpigwj+S7aw3mldNHVWbIPq9GncvVsgvlJYHu53WRzVfV7tAylt/ssSqHFv/p
1e6rC2m9hETHUZjSs8v0APWi8pfh15lJPqLS1lGF3QPVCPdc1t+8dBVnx8wnTgGV
E7FVLjmc3qIcXTR1TfcB4+X1ZnY5FNK7kR/kgIS64uZqntyNSW5W0SCFgWQClwwk
zVpo4v/YsCHiWI7cIuXHTTd7ntHJab1Og1jAPNEmENvEtza1SqGTSEUQs5wjS+p+
ii5E09b3Nufj1e8hJaodTxr+24hoFcYxWIgxhwIDAQABAoIBAQDa55WQCcWxkNZa
y5bVimBbZeif2+nKj8RTOZdGyzAAR0/K4c0iCa58jm4GfdkqftiUnrVIwY3dh/Ei
V1CZp4byggeUjs0rlmaZNYqMM/zKHtsMI9t4mf+vXNQI7wJVSJ+T5+5IesJLTqwf
DQo0ipXhQfxnAsqzA1ow9Ol8MCfdEeFL5SFaVNiNhany/7PR0letngwMIAje6arY
6dPzjPYDsCjMTjoNarUADIZeVxMdbOCmiMy+yGDZlgV80fK0+Fw81axot2KOufhy
CvqClUlJV5Csz2egHuThuVd39kX2vAAGXdPEUbXga1JTDB3cQiHXJo0hLUsUGniS
6KdsJOyRAoGBAOmNX/V7awLiVyWsRLTNr1IeoMwHeS3Wdt5/nedqh6p2fLpfbN1D
M8R29yqvNn1YmpZsDBhQlnddq7i7OEKjSODwNnLPys6fqalsIY/AIRJPOmHvVJl6
er4scaiGXA/FztvxGGQh4W92+Nb69dCXXXcG1QUljr+uGwrIGXc8aqzpAoGBAPil
4qGf/0s3Fv0tyLj+ZRWVjg5o4s/IsJyGRFc6RplU65y9VL9WsYvi5+d06w7lovac
omycJ/z+HiqTLcixdcHse54N9HHO5Ekui6cRbGAOdlzgS84TxYrmQK/FQkFvlcmO
rsfkDMmAgBc2yGP3N5IL60u90N2Kf3OiT5aHoCTvAoGAamdwioS6EkxQa+d6Pe1f
rMgrdgkJmmqVKXV22VHdkTn+RWLoVD4jvaR9o0LEToMpmtKLCCDfDG7up3EUhreh
ommOROyKd2yifX+4Iqfj6VWTQb8qCeqVNUNGXQMpuj3iqq3C8QvGi2PmpvsbNvdf
K7U/I+MikA2gYF8dywcJitECgYArDe5UNjQqffuJE2hyP/qY5jCW5ip/+Cw8rjMf
N4QKAN5bYZ1PFF/h7QRi26foCHNTaIPncpKqCAaJMLr4yWGulphBIgF1w3FcCqc7
4pR1fYuZQW1e3aWTC5Of2/RBCGVTZVV2X1KngYyseFvyk1gX/eBcWR3VfqnbB/vo
AMwGGQKBgB/MXrB2Z61WlxR0Y289k+5gjUSS9foZIkCjr1TFDvLbEStAW8Z3WFcS
Gxzjs5s45VO3+KRRd3591WHaPWcVdNze+y5ICUr4u3WSBKg8D+9H3cMKWq8QLCRP
bgMDGq8TzTLdGmb9vw3n5OG2aAQUQfwoAp+uUwh2EDMDYixYd2UJ
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0H28tj4Zgoj5y+/NF7Dhrf96+TQUolLR5j4YqoxbFRvgWtPE
0JFueoC0n6v4l58z4vog+1JjPHMO8rr0W3q9o/wWaY1CL3XqZJIkHI/fE/Eu1VLv
gLfDHsPevPNLczcpwXo7AadqxiUC/HRYZBJY+LA2OzWwO5ylx4YKnOzKxBXQ7a2q
qdpwZJSTTmGKyRx5pD6V5mCr47ITVAXqcjorTtJmG3zCFRhUjMscXIWC04S0IY/C
+cfMKaK4Jbqj1h2/9uNhbb55r9HdCEV/TLw+QL1Q+S6MWtng1S4Q3hyDXzB1LqfP
1brz7xl1pSLh+l0vF2pXom5zYiHlX4cAddR7MQIDAQABAoIBAAQtULeR/O7Zka+d
SU2dNJhI0wzlFzi9Ugk720CneTeuDEuljH7lOwJnS7cbOerHvMFiY4DFgMl4QKdq
SXT/u4bqiQRqWRYcVarYJrMPytdacKbDd5rrk5QtNmwwr6VKSKLgsQfyc7gui6XF
KvQuTewFk8CR7crz83pQ3CuSrulIwVny17HSE9NlXlv8SlHy7E2/a6H0UeOm7BRn
13An62CfRuBVFRd96oyTMpuNf2l+fcL5nnfM/F+tdr5MRR0pnEW2UKGdCbkTkAKd
AJnH2mmhWwbA8Gcx0Zn1X9/zu8Z3S6hTGnQmWIP25EcgipqKe/gocA9ervyR+0nb
FgwZZXECgYEA6D/mx3dI6AUY7RVIY9GexiJWLtS9O1d7PddmajoqVpwtjXT9jUf/
9BAAVWNKcIEs1t4foncJeYph6JnTrQjDz1LORC2kAjPUYhDQod/m6dqHxvjRAIns
v0NezDvWQ2v8CgklDjUc5hYtvqj51OkkbKpphRClAdJZvrlxBhXTT1UCgYEA5c/c
A6D3xIcGUrSVrjtLWUyJ9hszjjBrI5txtmwMzWDSkV3Hp1VhSfhCdIxqQxELxlxS
A8SOU0XKxRB7QiwpgHfnbGxtQ8tJ5JufIecwJkqRcD5fqQk69xva/76fNfes41L8
doNpFiu0fXbVdcB8UNN61VYsz31D/JD0qA9k5G0CgYEA5gq1egkrC7ZQ1DRqeYSd
8b79AnHx5Z9nEQAUD1ABs7wKWrzwkEoqugJHckxg5ULtuP5W80NY/SwWgqArTI8L
9IUejeVvOEdCLMhe/peaTzQHnQvDaPc0qtX+RelW9300LnSUYZg2QajiMqGIpF0x
mPjKf+TWrBFAl2tzCgYAQekCgYEAukqjaXWlI/To1UZ6R8DdNchr1cr7Ifpx/21U
4rH4NsyUJS7GWAlIUnQjOuNQiIla6DOScGd3kF11IAZaRKwUAIYyXZwPfvNeNSlJ
+Gu2hnPQLhMB7L8Ew6gbAVH/MfpSdfyhl1izaTuIlmQsacXdgI/OdP3kWVaMNEM1
cL755IkCgYAv10G49Mc57WBi6knRHM2cXbYVxjzedJf8zBnAYkQoLdRiYT6uj34U
hI1EajSfXYJ2t8PG5nbHMNHAEbYNBikhjDvSrZW+HaTmjsVGV25cqCHPy7giSqVS
VftcMlIX/MlTdfcYtNT4wHORZdO2P4wW/y5JSFNol4z3xB4TNXp55A==
-----END RSA PRIVATE KEY-----

View File

@ -16,6 +16,8 @@ import (
"io"
"net"
"net/url"
"os"
"path/filepath"
"time"
"github.com/talos-systems/crypto/x509"
@ -192,8 +194,6 @@ func (c *SystemClock) SetFixedTimestamp(t time.Time) {
}
// NewSecretsBundle creates secrets bundle generating all secrets or reading from the input options if provided.
//
//nolint:gocyclo
func NewSecretsBundle(clock Clock, opts ...GenOption) (*SecretsBundle, error) {
options := DefaultGenOptions()
@ -208,115 +208,256 @@ func NewSecretsBundle(clock Clock, opts ...GenOption) (*SecretsBundle, error) {
return options.Secrets, nil
}
bundle := SecretsBundle{
Clock: clock,
}
err := populateSecretsBundle(options.VersionContract, &bundle)
if err != nil {
return nil, err
}
return &bundle, nil
}
// NewSecretsBundleFromKubernetesPKI creates secrets bundle by reading the contents
// of a Kubernetes PKI directory (typically `/etc/kubernetes/pki`) and using the provided bootstrapToken as input.
//
//nolint:gocyclo
func NewSecretsBundleFromKubernetesPKI(pkiDir, bootstrapToken string, versionContract *config.VersionContract) (*SecretsBundle, error) {
dirStat, err := os.Stat(pkiDir)
if err != nil {
return nil, err
}
if !dirStat.IsDir() {
return nil, fmt.Errorf("%q is not a directory", pkiDir)
}
var (
etcd *x509.CertificateAuthority
kubernetesCA *x509.CertificateAuthority
aggregatorCA *x509.CertificateAuthority
serviceAccount *x509.ECDSAKey
talosCA *x509.CertificateAuthority
trustdInfo *TrustdInfo
kubeadmTokens *Secrets
err error
ca *x509.PEMEncodedCertificateAndKey
etcdCA *x509.PEMEncodedCertificateAndKey
aggregatorCA *x509.PEMEncodedCertificateAndKey
sa *x509.PEMEncodedKey
)
etcd, err = NewEtcdCA(clock.Now(), options.VersionContract)
ca, err = x509.NewCertificateAndKeyFromFiles(filepath.Join(pkiDir, "ca.crt"), filepath.Join(pkiDir, "ca.key"))
if err != nil {
return nil, err
}
kubernetesCA, err = NewKubernetesCA(clock.Now(), options.VersionContract)
err = validatePEMEncodedCertificateAndKey(ca)
if err != nil {
return nil, err
}
if options.VersionContract.SupportsAggregatorCA() {
aggregatorCA, err = NewAggregatorCA(clock.Now(), options.VersionContract)
etcdDir := filepath.Join(pkiDir, "etcd")
etcdCA, err = x509.NewCertificateAndKeyFromFiles(filepath.Join(etcdDir, "ca.crt"), filepath.Join(etcdDir, "ca.key"))
if err != nil {
return nil, err
}
err = validatePEMEncodedCertificateAndKey(etcdCA)
if err != nil {
return nil, err
}
aggregatorCACrtPath := filepath.Join(pkiDir, "front-proxy-ca.crt")
_, err = os.Stat(aggregatorCACrtPath)
aggregatorCAFound := err == nil
if aggregatorCAFound && !versionContract.SupportsAggregatorCA() {
return nil, fmt.Errorf("aggregator CA found in pki dir but is not supported by the requested version")
}
if versionContract.SupportsAggregatorCA() {
aggregatorCA, err = x509.NewCertificateAndKeyFromFiles(aggregatorCACrtPath, filepath.Join(pkiDir, "front-proxy-ca.key"))
if err != nil {
return nil, err
}
err = validatePEMEncodedCertificateAndKey(aggregatorCA)
if err != nil {
return nil, err
}
}
if options.VersionContract.SupportsServiceAccount() {
serviceAccount, err = x509.NewECDSAKey()
saKeyPath := filepath.Join(pkiDir, "sa.key")
_, err = os.Stat(saKeyPath)
saKeyFound := err == nil
if saKeyFound && !versionContract.SupportsServiceAccount() {
return nil, fmt.Errorf("service account key found in pki dir but is not supported by the requested version")
}
if versionContract.SupportsServiceAccount() {
var saBytes []byte
saBytes, err = os.ReadFile(filepath.Join(pkiDir, "sa.key"))
if err != nil {
return nil, err
}
sa = &x509.PEMEncodedKey{
Key: saBytes,
}
_, err = sa.GetKey()
if err != nil {
return nil, err
}
}
talosCA, err = NewTalosCA(clock.Now())
if err != nil {
return nil, err
}
kubeadmTokens = &Secrets{}
// Gen trustd token strings
kubeadmTokens.BootstrapToken, err = genToken(6, 16)
if err != nil {
return nil, err
}
kubeadmTokens.AESCBCEncryptionSecret, err = cis.CreateEncryptionToken()
if err != nil {
return nil, err
}
trustdInfo = &TrustdInfo{}
// Gen trustd token strings
trustdInfo.Token, err = genToken(6, 16)
if err != nil {
return nil, err
}
clusterID, err := randBytes(constants.DefaultClusterIDSize)
if err != nil {
return nil, fmt.Errorf("failed to generate cluster ID: %w", err)
}
clusterSecret, err := randBytes(constants.DefaultClusterSecretSize)
if err != nil {
return nil, fmt.Errorf("failed to generate cluster secret: %w", err)
}
result := &SecretsBundle{
Cluster: &Cluster{
ID: base64.URLEncoding.EncodeToString(clusterID),
Secret: base64.StdEncoding.EncodeToString(clusterSecret),
bundle := SecretsBundle{
Secrets: &Secrets{
BootstrapToken: bootstrapToken,
},
Clock: clock,
Secrets: kubeadmTokens,
TrustdInfo: trustdInfo,
Certs: &Certs{
Etcd: &x509.PEMEncodedCertificateAndKey{
Crt: etcd.CrtPEM,
Key: etcd.KeyPEM,
},
K8s: &x509.PEMEncodedCertificateAndKey{
Crt: kubernetesCA.CrtPEM,
Key: kubernetesCA.KeyPEM,
},
OS: &x509.PEMEncodedCertificateAndKey{
Crt: talosCA.CrtPEM,
Key: talosCA.KeyPEM,
},
Etcd: etcdCA,
K8s: ca,
K8sAggregator: aggregatorCA,
K8sServiceAccount: sa,
},
}
if aggregatorCA != nil {
result.Certs.K8sAggregator = &x509.PEMEncodedCertificateAndKey{
err = populateSecretsBundle(versionContract, &bundle)
if err != nil {
return nil, err
}
return &bundle, nil
}
// populateSecretsBundle fills all the missing fields in the secrets bundle.
//
//nolint:gocyclo,cyclop
func populateSecretsBundle(versionContract *config.VersionContract, bundle *SecretsBundle) error {
if bundle.Clock == nil {
bundle.Clock = NewClock()
}
if bundle.Certs == nil {
bundle.Certs = &Certs{}
}
if bundle.Certs.Etcd == nil {
etcd, err := NewEtcdCA(bundle.Clock.Now(), versionContract)
if err != nil {
return err
}
bundle.Certs.Etcd = &x509.PEMEncodedCertificateAndKey{
Crt: etcd.CrtPEM,
Key: etcd.KeyPEM,
}
}
if bundle.Certs.K8s == nil {
kubernetesCA, err := NewKubernetesCA(bundle.Clock.Now(), versionContract)
if err != nil {
return err
}
bundle.Certs.K8s = &x509.PEMEncodedCertificateAndKey{
Crt: kubernetesCA.CrtPEM,
Key: kubernetesCA.KeyPEM,
}
}
if versionContract.SupportsAggregatorCA() && bundle.Certs.K8sAggregator == nil {
aggregatorCA, err := NewAggregatorCA(bundle.Clock.Now(), versionContract)
if err != nil {
return err
}
bundle.Certs.K8sAggregator = &x509.PEMEncodedCertificateAndKey{
Crt: aggregatorCA.CrtPEM,
Key: aggregatorCA.KeyPEM,
}
}
if serviceAccount != nil {
result.Certs.K8sServiceAccount = &x509.PEMEncodedKey{
if versionContract.SupportsServiceAccount() && bundle.Certs.K8sServiceAccount == nil {
serviceAccount, err := x509.NewECDSAKey()
if err != nil {
return err
}
bundle.Certs.K8sServiceAccount = &x509.PEMEncodedKey{
Key: serviceAccount.KeyPEM,
}
}
return result, nil
if bundle.Certs.OS == nil {
talosCA, err := NewTalosCA(bundle.Clock.Now())
if err != nil {
return err
}
bundle.Certs.OS = &x509.PEMEncodedCertificateAndKey{
Crt: talosCA.CrtPEM,
Key: talosCA.KeyPEM,
}
}
if bundle.Secrets == nil {
bundle.Secrets = &Secrets{}
}
if bundle.Secrets.BootstrapToken == "" {
token, err := genToken(6, 16)
if err != nil {
return err
}
bundle.Secrets.BootstrapToken = token
}
if bundle.Secrets.AESCBCEncryptionSecret == "" {
aesCBCEncryptionSecret, err := cis.CreateEncryptionToken()
if err != nil {
return err
}
bundle.Secrets.AESCBCEncryptionSecret = aesCBCEncryptionSecret
}
if bundle.TrustdInfo == nil {
bundle.TrustdInfo = &TrustdInfo{}
}
if bundle.TrustdInfo.Token == "" {
token, err := genToken(6, 16)
if err != nil {
return err
}
bundle.TrustdInfo.Token = token
}
if bundle.Cluster == nil {
bundle.Cluster = &Cluster{}
}
if bundle.Cluster.ID == "" {
clusterID, err := randBytes(constants.DefaultClusterIDSize)
if err != nil {
return fmt.Errorf("failed to generate cluster ID: %w", err)
}
bundle.Cluster.ID = base64.URLEncoding.EncodeToString(clusterID)
}
if bundle.Cluster.Secret == "" {
clusterSecret, err := randBytes(constants.DefaultClusterSecretSize)
if err != nil {
return fmt.Errorf("failed to generate cluster secret: %w", err)
}
bundle.Cluster.Secret = base64.StdEncoding.EncodeToString(clusterSecret)
}
return nil
}
// NewSecretsBundleFromConfig creates secrets bundle using existing config.
@ -608,3 +749,14 @@ func randBytes(size int) ([]byte, error) {
return buf, nil
}
func validatePEMEncodedCertificateAndKey(certs *x509.PEMEncodedCertificateAndKey) error {
_, err := certs.GetKey()
if err != nil {
return err
}
_, err = certs.GetCert()
return err
}

View File

@ -18,7 +18,7 @@ import (
// GenOption controls generate options specific to input generation.
type GenOption func(o *GenOptions) error
// WithEndpointList specifies endpoints to use when acessing Talos cluster.
// WithEndpointList specifies endpoints to use when accessing Talos cluster.
func WithEndpointList(endpoints []string) GenOption {
return func(o *GenOptions) error {
o.EndpointList = endpoints

View File

@ -1253,9 +1253,11 @@ talosctl gen secrets [flags]
### Options
```
-h, --help help for secrets
-o, --output-file string path of the output file (default "secrets.yaml")
--talos-version string the desired Talos version to generate secrets bundle for (backwards compatibility, e.g. v0.8)
-p, --from-kubernetes-pki string use a Kubernetes PKI directory (e.g. /etc/kubernetes/pki) as input
-h, --help help for secrets
-t, --kubernetes-bootstrap-token string use the provided bootstrap token as input
-o, --output-file string path of the output file (default "secrets.yaml")
--talos-version string the desired Talos version to generate secrets bundle for (backwards compatibility, e.g. v0.8)
```
### Options inherited from parent commands