mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
This was fixed running the `misspell` linter in fix mode using
`gometalinter`. The exact command I ran was :
```
gometalinter --vendor --disable-all -E misspell --linter='misspell:misspell -w {path}:^(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$' ./...
```
Some typo were fixed by hand on top of it.
390 lines
12 KiB
Go
390 lines
12 KiB
Go
/*
|
|
Copyright 2015-2017 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/gravitational/kingpin"
|
|
"github.com/gravitational/teleport/lib/auth"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/service"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
"github.com/gravitational/trace"
|
|
kyaml "k8s.io/apimachinery/pkg/util/yaml"
|
|
)
|
|
|
|
type ResourceCreateHandler func(*auth.TunClient, services.UnknownResource) error
|
|
type ResourceKind string
|
|
|
|
// ResourceCommand implements `tctl get/create/list` commands for manipulating
|
|
// Teleport resources
|
|
type ResourceCommand struct {
|
|
config *service.Config
|
|
ref services.Ref
|
|
format string
|
|
namespace string
|
|
withSecrets bool
|
|
force bool
|
|
|
|
// filename is the name of the resource, used for 'create'
|
|
filename string
|
|
|
|
// CLI subcommands:
|
|
deleteCmd *kingpin.CmdClause
|
|
getCmd *kingpin.CmdClause
|
|
createCmd *kingpin.CmdClause
|
|
|
|
CreateHandlers map[ResourceKind]ResourceCreateHandler
|
|
}
|
|
|
|
const getHelp = `Examples:
|
|
|
|
$ tctl get clusters : prints the list of all trusted clusters
|
|
$ tctl get cluster/east : prints the trusted cluster 'east'
|
|
|
|
Same as above, but using JSON output:
|
|
|
|
$ tctl get clusters --format=json
|
|
`
|
|
|
|
// Initialize allows ResourceCommand to plug itself into the CLI parser
|
|
func (g *ResourceCommand) Initialize(app *kingpin.Application, config *service.Config) {
|
|
g.CreateHandlers = map[ResourceKind]ResourceCreateHandler{
|
|
services.KindUser: g.createUser,
|
|
services.KindTrustedCluster: g.createTrustedCluster,
|
|
}
|
|
g.config = config
|
|
|
|
g.createCmd = app.Command("create", "Create or update a Teleport resource from a YAML file")
|
|
g.createCmd.Arg("filename", "resource definition file").Required().StringVar(&g.filename)
|
|
g.createCmd.Flag("force", "Overwrite the resource if already exists").Short('f').BoolVar(&g.force)
|
|
|
|
g.deleteCmd = app.Command("rm", "Delete a resource").Alias("del")
|
|
g.deleteCmd.Arg("resource", "Resource to delete").SetValue(&g.ref)
|
|
|
|
g.getCmd = app.Command("get", "Print a YAML declaration of various Teleport resources")
|
|
g.getCmd.Arg("resource", "Resource spec: 'type/[name]'").SetValue(&g.ref)
|
|
g.getCmd.Flag("format", "Output format: 'yaml' or 'json'").Default(formatYAML).StringVar(&g.format)
|
|
g.getCmd.Flag("namespace", "Namespace of the resources").Hidden().Default(defaults.Namespace).StringVar(&g.namespace)
|
|
g.getCmd.Flag("with-secrets", "Include secrets in resources like certificate authorities or OIDC connectors").Default("false").BoolVar(&g.withSecrets)
|
|
|
|
g.getCmd.Alias(getHelp)
|
|
}
|
|
|
|
// 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 (g *ResourceCommand) TryRun(cmd string, client *auth.TunClient) (match bool, err error) {
|
|
switch cmd {
|
|
// tctl get
|
|
case g.getCmd.FullCommand():
|
|
err = g.Get(client)
|
|
// tctl create
|
|
case g.createCmd.FullCommand():
|
|
err = g.Create(client)
|
|
// tctl rm
|
|
case g.deleteCmd.FullCommand():
|
|
err = g.Delete(client)
|
|
default:
|
|
return false, nil
|
|
}
|
|
return true, trace.Wrap(err)
|
|
}
|
|
|
|
// IsDeleteSubcommand returns 'true' if the given command is `tctl rm`
|
|
func (g *ResourceCommand) IsDeleteSubcommand(cmd string) bool {
|
|
return cmd == g.deleteCmd.FullCommand()
|
|
}
|
|
|
|
// GetRef returns the reference (basically type/name pair) of the resource
|
|
// the command is operating on
|
|
func (g *ResourceCommand) GetRef() services.Ref {
|
|
return g.ref
|
|
}
|
|
|
|
// Get prints one or many resources of a certain type
|
|
func (g *ResourceCommand) Get(client *auth.TunClient) error {
|
|
collection, err := g.getCollection(client)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
switch g.format {
|
|
case formatYAML:
|
|
return collection.writeYAML(os.Stdout)
|
|
|
|
// NOTE: only YAML is officially supported. Text and JSON are for experimentation only!
|
|
case formatText:
|
|
return collection.writeText(os.Stdout)
|
|
case formatJSON:
|
|
return collection.writeJSON(os.Stdout)
|
|
}
|
|
return trace.BadParameter("unsupported format")
|
|
}
|
|
|
|
// Create updates or insterts one or many resources
|
|
func (u *ResourceCommand) Create(client *auth.TunClient) error {
|
|
reader, err := utils.OpenFile(u.filename)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
decoder := kyaml.NewYAMLOrJSONDecoder(reader, 32*1024)
|
|
count := 0
|
|
for {
|
|
var raw services.UnknownResource
|
|
err := decoder.Decode(&raw)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
if count == 0 {
|
|
return trace.BadParameter("no resources found, empty input?")
|
|
}
|
|
return nil
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
count += 1
|
|
|
|
// locate the creator function for a given resource kind:
|
|
creator, found := u.CreateHandlers[ResourceKind(raw.Kind)]
|
|
if !found {
|
|
return trace.BadParameter("creating resources of type %q is not supported", raw.Kind)
|
|
}
|
|
return creator(client, raw)
|
|
}
|
|
}
|
|
|
|
// createTrustedCluster implements `tctl create cluster.yaml` command
|
|
func (u *ResourceCommand) createTrustedCluster(client *auth.TunClient, raw services.UnknownResource) error {
|
|
tc, err := services.GetTrustedClusterMarshaler().Unmarshal(raw.Raw)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// check if such cluster already exists:
|
|
name := tc.GetName()
|
|
_, err = client.GetTrustedCluster(name)
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
exists := (err == nil)
|
|
if u.force == false && exists {
|
|
return trace.AlreadyExists("trusted cluster '%s' already exists", name)
|
|
}
|
|
if err := client.UpsertTrustedCluster(tc); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("trusted cluster '%s' has been %s\n", name, UpsertVerb(exists))
|
|
return nil
|
|
}
|
|
|
|
// createUser implements 'tctl create user.yaml' command
|
|
func (u *ResourceCommand) createUser(client *auth.TunClient, raw services.UnknownResource) error {
|
|
user, err := services.GetUserMarshaler().UnmarshalUser(raw.Raw)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
userName := user.GetName()
|
|
// see if a user with such name exists:
|
|
_, err = client.GetUser(userName)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// only 'update' is supported (resources API do not support generating
|
|
// invitation tokens, 2FA and sign-up URLs)
|
|
if u.force == false {
|
|
return trace.BadParameter("you must use 'tctl users add' to create new users")
|
|
}
|
|
if err := client.UpsertUser(user); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("user '%s' has been updated\n", userName)
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes resource by name
|
|
func (d *ResourceCommand) Delete(client *auth.TunClient) (err error) {
|
|
if d.ref.Kind == "" || d.ref.Name == "" {
|
|
return trace.BadParameter("provide a full resource name to delete, for example:\n$ tctl rm cluster/east\n")
|
|
}
|
|
|
|
switch d.ref.Kind {
|
|
case services.KindUser:
|
|
if err = client.DeleteUser(d.ref.Name); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("user %v has been deleted\n", d.ref.Name)
|
|
case services.KindSAMLConnector:
|
|
if err = client.DeleteSAMLConnector(d.ref.Name); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("SAML Connector %v has been deleted\n", d.ref.Name)
|
|
case services.KindOIDCConnector:
|
|
if err = client.DeleteOIDCConnector(d.ref.Name); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("OIDC Connector %v has been deleted\n", d.ref.Name)
|
|
case services.KindReverseTunnel:
|
|
if err := client.DeleteReverseTunnel(d.ref.Name); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("reverse tunnel %v has been deleted\n", d.ref.Name)
|
|
case services.KindTrustedCluster:
|
|
if err = client.DeleteTrustedCluster(d.ref.Name); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
fmt.Printf("trusted cluster %q has been deleted\n", d.ref.Name)
|
|
default:
|
|
return trace.BadParameter("deleting resources of type %q is not supported", d.ref.Kind)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsForced returns true if -f flag was passed
|
|
func (cmd *ResourceCommand) IsForced() bool {
|
|
return cmd.force
|
|
}
|
|
|
|
func (g *ResourceCommand) getCollection(client auth.ClientI) (c ResourceCollection, err error) {
|
|
if g.ref.Kind == "" {
|
|
return nil, trace.BadParameter("specify resource to list, e.g. 'tctl get roles'")
|
|
}
|
|
switch g.ref.Kind {
|
|
// load user(s)
|
|
case services.KindUser:
|
|
var users services.Users
|
|
// just one?
|
|
if !g.ref.IsEmtpy() {
|
|
user, err := client.GetUser(g.ref.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
users = services.Users{user}
|
|
// all of them?
|
|
} else {
|
|
users, err = client.GetUsers()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
return &userCollection{users: users}, nil
|
|
case services.KindSAMLConnector:
|
|
connectors, err := client.GetSAMLConnectors(g.withSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &samlCollection{connectors: connectors}, nil
|
|
case services.KindOIDCConnector:
|
|
connectors, err := client.GetOIDCConnectors(g.withSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &oidcCollection{connectors: connectors}, nil
|
|
case services.KindReverseTunnel:
|
|
tunnels, err := client.GetReverseTunnels()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &reverseTunnelCollection{tunnels: tunnels}, nil
|
|
case services.KindCertAuthority:
|
|
userAuthorities, err := client.GetCertAuthorities(services.UserCA, g.withSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
hostAuthorities, err := client.GetCertAuthorities(services.HostCA, g.withSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userAuthorities = append(userAuthorities, hostAuthorities...)
|
|
return &authorityCollection{cas: userAuthorities}, nil
|
|
case services.KindNode:
|
|
nodes, err := client.GetNodes(g.namespace)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &serverCollection{servers: nodes}, nil
|
|
case services.KindAuthServer:
|
|
servers, err := client.GetAuthServers()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &serverCollection{servers: servers}, nil
|
|
case services.KindProxy:
|
|
servers, err := client.GetAuthServers()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &serverCollection{servers: servers}, nil
|
|
case services.KindRole:
|
|
if g.ref.Name == "" {
|
|
roles, err := client.GetRoles()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &roleCollection{roles: roles}, nil
|
|
}
|
|
role, err := client.GetRole(g.ref.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &roleCollection{roles: []services.Role{role}}, nil
|
|
case services.KindNamespace:
|
|
if g.ref.Name == "" {
|
|
namespaces, err := client.GetNamespaces()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &namespaceCollection{namespaces: namespaces}, nil
|
|
}
|
|
ns, err := client.GetNamespace(g.ref.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &namespaceCollection{namespaces: []services.Namespace{*ns}}, nil
|
|
case services.KindTrustedCluster:
|
|
if g.ref.Name == "" {
|
|
trustedClusters, err := client.GetTrustedClusters()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &trustedClusterCollection{trustedClusters: trustedClusters}, nil
|
|
}
|
|
trustedCluster, err := client.GetTrustedCluster(g.ref.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &trustedClusterCollection{trustedClusters: []services.TrustedCluster{trustedCluster}}, nil
|
|
}
|
|
return nil, trace.BadParameter("'%v' is not supported", g.ref.Kind)
|
|
}
|
|
|
|
const (
|
|
formatYAML = "yaml"
|
|
formatText = "text"
|
|
formatJSON = "json"
|
|
)
|
|
|
|
func UpsertVerb(exists bool) string {
|
|
if exists {
|
|
return "updated"
|
|
} else {
|
|
return "created"
|
|
}
|
|
}
|