From 51869b18e0d59d39b6ac72381f411bb094459f82 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Mon, 11 Oct 2021 12:33:18 +0300 Subject: [PATCH] Invalidate and remove join tokens after use (#207) * Invalidate and remove join tokens after worker install is finished * Use "token invalidate", skip when "--no-wait" * Needs sudo * Add trace logging * Prototype token decoding * More prototyping * Fancy token decoder * Wrong pkg * Lint * There was a test also that was forgotten to add --- config/cluster/k0s.go | 53 ++++++++++++++++++++++++++++++++++++ config/cluster/k0s_test.go | 15 ++++++++++ phase/install_controllers.go | 17 ++++++++++++ phase/install_workers.go | 23 ++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 config/cluster/k0s_test.go diff --git a/config/cluster/k0s.go b/config/cluster/k0s.go index 38fffe53..3b690e47 100644 --- a/config/cluster/k0s.go +++ b/config/cluster/k0s.go @@ -1,6 +1,10 @@ package cluster import ( + "compress/gzip" + "encoding/base64" + "fmt" + "io" "strings" "time" @@ -10,6 +14,7 @@ import ( "github.com/k0sproject/k0sctl/integration/github" "github.com/k0sproject/k0sctl/version" "github.com/k0sproject/rig/exec" + "gopkg.in/yaml.v2" ) // K0sMinVersion is the minimum k0s version supported @@ -77,3 +82,51 @@ func (k K0s) GenerateToken(h *Host, role string, expiry time.Duration) (token st func (k K0s) GetClusterID(h *Host) (string, error) { return h.ExecOutput(h.Configurer.KubectlCmdf("get -n kube-system namespace kube-system -o template={{.metadata.uid}}"), exec.Sudo(h)) } + +// TokenID returns a token id from a token string that can be used to invalidate the token +func TokenID(s string) (string, error) { + b64 := make([]byte, base64.StdEncoding.DecodedLen(len(s))) + _, err := base64.StdEncoding.Decode(b64, []byte(s)) + if err != nil { + return "", fmt.Errorf("failed to decode token: %w", err) + } + + sr := strings.NewReader(s) + b64r := base64.NewDecoder(base64.StdEncoding, sr) + gzr, err := gzip.NewReader(b64r) + if err != nil { + return "", fmt.Errorf("failed to create a reader for token: %w", err) + } + defer gzr.Close() + + c, err := io.ReadAll(gzr) + if err != nil { + return "", fmt.Errorf("failed to uncompress token: %w", err) + } + cfg := dig.Mapping{} + err = yaml.Unmarshal(c, &cfg) + if err != nil { + return "", fmt.Errorf("failed to unmarshal token: %w", err) + } + + users, ok := cfg.Dig("users").([]interface{}) + if !ok || len(users) < 1 { + return "", fmt.Errorf("failed to find users in token") + } + + user, ok := users[0].(dig.Mapping) + if !ok { + return "", fmt.Errorf("failed to find user in token") + } + + token, ok := user.Dig("user", "token").(string) + if !ok { + return "", fmt.Errorf("failed to find user token in token") + } + + idx := strings.IndexRune(token, '.') + if idx < 0 { + return "", fmt.Errorf("failed to find separator in token") + } + return token[0:idx], nil +} diff --git a/config/cluster/k0s_test.go b/config/cluster/k0s_test.go new file mode 100644 index 00000000..f196169b --- /dev/null +++ b/config/cluster/k0s_test.go @@ -0,0 +1,15 @@ +package cluster + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTokenID(t *testing.T) { + token := "H4sIAAAAAAAC/2xVXY/iOBZ9r1/BH6geO4GeAWkfKiEmGGLKjn1N/BbidAFOgjuk+Frtf18V3SPtSvN2fc/ROdaVfc9L6Q9Q9+fDqZuNLvilaj7PQ92fZy+vo9/17GU0Go3OdX+p+9loPwz+PPvjD/xn8A3/+Q19C2bfx+Pwyanqfjj8OFTlUL+Wn8P+1B+G+6sth3I2WudoWOc4FspSeYjmAqjKlaEcESWeGBpih2muRCQSNucavEEkzBWNDGoApDV1t19W6uNSbJsyRzS1mPc7TVdiDknV0qNFQmjl1zvsaZmao3RECHVd8YZEFtlEgGW8ISmXBIQiY6km+wwbr5v9yoIvVHs71pL81CAio0yYpQ2DJMFSe1InWHEZMZHQveiqa/3hf2Eg+v/FpKJdnZifHCA2aKK5IwwSsbVzYnZgJkWLdUZ8IbfCZA5CE1hSKhxliZ2rkKRxw2hxZIlSEHMgwFWCckUTi8iTmyNy+ZqJUtktO2Y9C8Wpuk8DsTUT7ehnjt9uBTQ0T7yDB9nyw+A4Tlb5wt2NbHgB5LSJpwvR2Ytpp6oKm/lG2ZvUZoDERjs9vubzamxJcZEaX6vDwLKWFeUWIoOqi7z/hWx7c2q77DfcJ5BkQQFAyxYw6xix8BZILAar8Ha3GM7l420ssZ/UZE/rrQtUytSus4ssXGKOissKkdgiOskw1fowPKRqxnFLPy0hj1pPvV6IC0t4AOhGgZDlZjFdGYdXLBVZBozKrUccW6Ra2mQNm5sF9bsHXRVqv8lB7E3XmNyZjKHTSm7Jp82HyxoJDom56HY8zgFa6/xCoOtdIL8qF8t71rDUYBZAI247ZHnpiluZn+9WNu8GsvEusFuOpvNS20J/+GUN1aN2U2kfpFQouVaBj3PsW6VgXwXVeJfSd4DlLdN2JR+gqoAed8hEBcB7OXc4J3Dl2jLuSCQCL0pHo9jhiCU2ygCcSC3hh2moFEQWNTFvfaQS2snGLJXDMdfFWCiquBKRUh8XqZZXgZIbaJEYTLbcUQnBtLDkY8VbWuzmMAhH97ka1tWWKN1lvQFLICEb3tq+0vu+VNXEPqKvN/gQjkQSsejLv3BsUjTRNk8mpNbMF46d1Ju/SURPRWihBOJtS5eVwp9ZQhvIB8+UCo1ksSXg7IPcS2wNc35cphHKVKNE4rebbSR2ODpxd5uYAA/VfH+JW9Jt1GRv231eJ9mj1uao2+Z7pRrB2ulP4+xF5kOxDtUF3PLKJXmXCb4XgQmzuRFVmmGZnCaA/nrIBdCvuRduvMpVs8lcNi7UcDVhRG0A93JLYpP66yqYgJoLoZumlQ9x2xFD8znIkux77oacdWqSdZSVyjCWnkKmb+9WDz/Nh5+b9O1SIDIUHaC6bW5V4qFsYSnSRmUIloXCuV1MaE7IsQAxBkR5ndqASRZtFDVGm7VszHGzwEfhJqzUzTV2tMi1iG369dfsmjVvkxKKfhMPgjsccEUPLMmCTcJCsTDrfGHGdXsOJcBpo4ezQd7sQroC3EQrdLtVD+Z16lZCY58rEO8SrX7vZiId/+AIckiaRa5YBIl67uU1P/3rZTTqyraejRw6v1Snbqhvw6+U+FX/Som/I+PJ+mp8np+nz13d1MPr7nQazkNf+v9X++z7uhte/1Z6Nt2hs7NRfOp+HD5efF//qPu6q+rzbPTv/7x8qT7Nf4v8g/zT+HmF4eTqbjY6fD+E949vVzeZ7vHx8mM6uPCATi//DQAA//+MVAsnAgcAAA==" + + id, err := TokenID(token) + require.NoError(t, err) + require.Equal(t, "i6i3yg", id) +} diff --git a/phase/install_controllers.go b/phase/install_controllers.go index e9695e68..c55bd61a 100644 --- a/phase/install_controllers.go +++ b/phase/install_controllers.go @@ -5,6 +5,7 @@ import ( "github.com/k0sproject/k0sctl/config" "github.com/k0sproject/k0sctl/config/cluster" + "github.com/k0sproject/rig/exec" log "github.com/sirupsen/logrus" ) @@ -60,12 +61,28 @@ func (p *InstallControllers) Run() error { if err != nil { return err } + tokenID, err := cluster.TokenID(token) + if err != nil { + return err + } + log.Debugf("%s: join token ID: %s", p.leader, tokenID) + defer func() { + if err := p.leader.Exec(p.leader.Configurer.K0sCmdf("token invalidate %s", tokenID), exec.Sudo(p.leader), exec.RedactString(token)); err != nil { + log.Warnf("%s: failed to invalidate the controller join token", p.leader) + } + }() log.Infof("%s: writing join token", h) if err := h.Configurer.WriteFile(h, h.K0sJoinTokenPath(), token, "0640"); err != nil { return err } + defer func() { + if err := h.Configurer.DeleteFile(h, h.K0sJoinTokenPath()); err != nil { + log.Warnf("%s: failed to clean up the join token file at %s", h, h.K0sJoinTokenPath()) + } + }() + log.Infof("%s: installing k0s controller", h) if err := h.Exec(h.K0sInstallCommand()); err != nil { return err diff --git a/phase/install_workers.go b/phase/install_workers.go index 510fdf1f..e74f4c95 100644 --- a/phase/install_workers.go +++ b/phase/install_workers.go @@ -6,6 +6,7 @@ import ( "github.com/k0sproject/k0sctl/config" "github.com/k0sproject/k0sctl/config/cluster" + "github.com/k0sproject/rig/exec" log "github.com/sirupsen/logrus" ) @@ -76,12 +77,34 @@ func (p *InstallWorkers) Run() error { return err } + tokenID, err := cluster.TokenID(token) + if err != nil { + return err + } + log.Debugf("%s: join token ID: %s", p.leader, tokenID) + + if !NoWait { + defer func() { + if err := p.leader.Exec(p.leader.Configurer.K0sCmdf("token invalidate %s", tokenID), exec.Sudo(p.leader), exec.RedactString(token)); err != nil { + log.Warnf("%s: failed to invalidate the worker join token", p.leader) + } + }() + } + return p.hosts.ParallelEach(func(h *cluster.Host) error { log.Infof("%s: writing join token", h) if err := h.Configurer.WriteFile(h, h.K0sJoinTokenPath(), token, "0640"); err != nil { return err } + if !NoWait { + defer func() { + if err := h.Configurer.DeleteFile(h, h.K0sJoinTokenPath()); err != nil { + log.Warnf("%s: failed to clean up the join token file at %s", h, h.K0sJoinTokenPath()) + } + }() + } + if sp, err := h.Configurer.ServiceScriptPath(h, h.K0sServiceName()); err == nil { if h.Configurer.ServiceIsRunning(h, h.K0sServiceName()) { log.Infof("%s: stopping service", h)