Skip to content

Commit

Permalink
[v17] feat: interactive tctl auth rotate
Browse files Browse the repository at this point in the history
Backport #49171 to branch/v17

changelog: Added an interactive mode for tctl auth rotate
  • Loading branch information
nklaassen committed Dec 6, 2024
1 parent fc7c2be commit 334f07d
Show file tree
Hide file tree
Showing 7 changed files with 1,424 additions and 73 deletions.
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ require (
github.com/buildkite/bintest/v3 v3.3.0
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.0
github.com/charmbracelet/huh v0.6.0
github.com/charmbracelet/lipgloss v0.13.0
github.com/coreos/go-oidc v2.2.1+incompatible // replaced
github.com/coreos/go-oidc/v3 v3.11.0
Expand Down Expand Up @@ -266,6 +267,7 @@ require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/apache/arrow/go/v15 v15.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect
Expand All @@ -285,11 +287,13 @@ require (
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/cloudflare/cfssl v1.6.4 // indirect
github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 // indirect
Expand Down Expand Up @@ -432,6 +436,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.4.0 // indirect
Expand All @@ -442,7 +447,7 @@ require (
github.com/mtibben/percent v0.2.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
Expand Down
14 changes: 12 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.49.12/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
Expand Down Expand Up @@ -983,6 +985,8 @@ github.com/buildkite/interpolate v0.1.3/go.mod h1:UNVe6A+UfiBNKbhAySrBbZFZFxQ+DX
github.com/buildkite/roko v1.2.0 h1:hbNURz//dQqNl6Eo9awjQOVOZwSDJ8VEbBDxSfT9rGQ=
github.com/buildkite/roko v1.2.0/go.mod h1:23R9e6nHxgedznkwwfmqZ6+0VJZJZ2Sg/uVcp2cP46I=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
Expand All @@ -1003,10 +1007,14 @@ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQW
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
Expand Down Expand Up @@ -1822,6 +1830,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
Expand Down Expand Up @@ -1858,8 +1868,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
Expand Down
42 changes: 35 additions & 7 deletions lib/asciitable/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ package asciitable
import (
"bytes"
"fmt"
"io"
"os"
"slices"
"strings"
"text/tabwriter"

"github.com/gravitational/trace"
"golang.org/x/term"
)

Expand Down Expand Up @@ -158,10 +160,26 @@ func (t *Table) truncateCell(colIndex int, cell string) (string, bool) {
}

// AsBuffer returns a *bytes.Buffer with the printed output of the table.
//
// TODO(nklaassen): delete this, all calls either immediately copy the buffer to
// another writer or just call .String() once.
func (t *Table) AsBuffer() *bytes.Buffer {
var buffer bytes.Buffer
// Writes to bytes.Buffer never return an error.
_ = t.WriteTo(&buffer)
return &buffer
}

writer := tabwriter.NewWriter(&buffer, 5, 0, 1, ' ', 0)
func (t *Table) String() string {
var sb strings.Builder
// Writes to strings.Builder never return an error.
_ = t.WriteTo(&sb)
return sb.String()
}

// WriteTo writes the full table to [w] or else returns an error.
func (t *Table) WriteTo(w io.Writer) error {
writer := tabwriter.NewWriter(w, 5, 0, 1, ' ', 0)
template := strings.Repeat("%v\t", len(t.columns))

// Header and separator.
Expand All @@ -173,8 +191,12 @@ func (t *Table) AsBuffer() *bytes.Buffer {
colh = append(colh, col.Title)
cols = append(cols, strings.Repeat("-", col.width))
}
fmt.Fprintf(writer, template+"\n", colh...)
fmt.Fprintf(writer, template+"\n", cols...)
if _, err := fmt.Fprintf(writer, template+"\n", colh...); err != nil {
return trace.Wrap(err)
}
if _, err := fmt.Fprintf(writer, template+"\n", cols...); err != nil {
return trace.Wrap(err)
}
}

// Body.
Expand All @@ -188,17 +210,23 @@ func (t *Table) AsBuffer() *bytes.Buffer {
}
rowi = append(rowi, cell)
}
fmt.Fprintf(writer, template+"\n", rowi...)
if _, err := fmt.Fprintf(writer, template+"\n", rowi...); err != nil {
return trace.Wrap(err)
}
}

// Footnotes.
for label := range footnoteLabels {
fmt.Fprintln(writer)
fmt.Fprintln(writer, label, t.footnotes[label])
if _, err := fmt.Fprintln(writer); err != nil {
return trace.Wrap(err)
}
if _, err := fmt.Fprintln(writer, label, t.footnotes[label]); err != nil {
return trace.Wrap(err)
}
}

writer.Flush()
return &buffer
return nil
}

// IsHeadless returns true if none of the table title cells contains any text.
Expand Down
4 changes: 2 additions & 2 deletions lib/services/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func ParseShortcut(in string) (string, error) {
return types.KindKubeServer, nil
case types.KindLock, "locks":
return types.KindLock, nil
case types.KindDatabaseServer:
case types.KindDatabaseServer, "db_servers":
return types.KindDatabaseServer, nil
case types.KindNetworkRestrictions:
return types.KindNetworkRestrictions, nil
Expand All @@ -185,7 +185,7 @@ func ParseShortcut(in string) (string, error) {
return types.KindApp, nil
case types.KindAppServer, "app_servers":
return types.KindAppServer, nil
case types.KindWindowsDesktopService, "windows_service", "win_desktop_service", "win_service":
case types.KindWindowsDesktopService, "windows_service", "win_desktop_service", "win_service", "windows_desktop_services":
return types.KindWindowsDesktopService, nil
case types.KindWindowsDesktop, "win_desktop":
return types.KindWindowsDesktop, nil
Expand Down
43 changes: 5 additions & 38 deletions tool/tctl/common/auth_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,11 @@ type AuthCommand struct {
streamTarfile bool
identityWriter identityfile.ConfigWriter

rotateGracePeriod time.Duration
rotateType string
rotateManualMode bool
rotateTargetPhase string
authRotate authRotateCommand

authGenerate *kingpin.CmdClause
authExport *kingpin.CmdClause
authSign *kingpin.CmdClause
authRotate *kingpin.CmdClause
authLS *kingpin.CmdClause
authCRL *kingpin.CmdClause
// testInsecureSkipVerify is used to skip TLS verification during tests
Expand Down Expand Up @@ -155,13 +151,7 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *servicecfg.Co
a.authSign.Flag("windows-sid", `Optional Security Identifier to embed in the certificate. Only used when --format is set to "windows"`).StringVar(&a.windowsSID)
a.authSign.Flag("omit-cdp", `Omit CRL Distribution Points from the cert. Only used when --format is set to "windows"`).BoolVar(&a.omitCDP)

a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster.")
a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to re-login and nodes to re-register.").
Default(fmt.Sprintf("%v", defaults.RotationGracePeriod)).
DurationVar(&a.rotateGracePeriod)
a.authRotate.Flag("manual", "Activate manual rotation , set rotation phases manually").BoolVar(&a.rotateManualMode)
a.authRotate.Flag("type", fmt.Sprintf("Certificate authority to rotate, one of: %s", strings.Join(getCertAuthTypes(), ", "))).Required().EnumVar(&a.rotateType, getCertAuthTypes()...)
a.authRotate.Flag("phase", fmt.Sprintf("Target rotation phase to set, used in manual rotation, one of: %v", strings.Join(types.RotatePhases, ", "))).StringVar(&a.rotateTargetPhase)
a.authRotate.Initialize(auth)

a.authLS = auth.Command("ls", "List connected auth servers.")
a.authLS.Flag("format", "Output format: 'yaml', 'json' or 'text'").Default(teleport.YAML).StringVar(&a.format)
Expand All @@ -173,15 +163,16 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *servicecfg.Co
// TryRun takes the CLI command as an argument (like "auth gen") and executes it
// or returns match=false if 'cmd' does not belong to it
func (a *AuthCommand) TryRun(ctx context.Context, cmd string, client *authclient.Client) (match bool, err error) {
if match, err := a.authRotate.TryRun(ctx, cmd, client); match || err != nil {
return match, trace.Wrap(err)
}
switch cmd {
case a.authGenerate.FullCommand():
err = a.GenerateKeys(ctx, client)
case a.authExport.FullCommand():
err = a.ExportAuthorities(ctx, client)
case a.authSign.FullCommand():
err = a.GenerateAndSignKeys(ctx, client)
case a.authRotate.FullCommand():
err = a.RotateCertAuthority(ctx, client)
case a.authLS.FullCommand():
err = a.ListAuthServers(ctx, client)
case a.authCRL.FullCommand():
Expand Down Expand Up @@ -427,30 +418,6 @@ func (a *AuthCommand) generateSnowflakeKey(ctx context.Context, clusterAPI certi
writeHelperMessageDBmTLS(a.helperMsgDst(), filesWritten, "", a.outputFormat, "", a.streamTarfile))
}

// RotateCertAuthority starts or restarts certificate authority rotation process
func (a *AuthCommand) RotateCertAuthority(ctx context.Context, client *authclient.Client) error {
req := types.RotateRequest{
Type: types.CertAuthType(a.rotateType),
GracePeriod: &a.rotateGracePeriod,
TargetPhase: a.rotateTargetPhase,
}
if a.rotateManualMode {
req.Mode = types.RotationModeManual
} else {
req.Mode = types.RotationModeAuto
}
if err := client.RotateCertAuthority(ctx, req); err != nil {
return err
}
if a.rotateTargetPhase != "" {
fmt.Printf("Updated rotation phase to %q. To check status use 'tctl status'\n", a.rotateTargetPhase)
} else {
fmt.Printf("Initiated certificate authority rotation. To check status use 'tctl status'\n")
}

return nil
}

// ListAuthServers prints a list of connected auth servers
func (a *AuthCommand) ListAuthServers(ctx context.Context, clusterAPI *authclient.Client) error {
servers, err := clusterAPI.GetAuthServers()
Expand Down
Loading

0 comments on commit 334f07d

Please sign in to comment.