Skip to content

Commit

Permalink
Merge pull request #1615 from gravitational/sasha/tokens
Browse files Browse the repository at this point in the history
Add support for custom tokens.
  • Loading branch information
klizhentas authored Jan 18, 2018
2 parents b40e5e4 + 5d134b4 commit 15de8a5
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 54 deletions.
9 changes: 2 additions & 7 deletions lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,17 +875,12 @@ func (s *APIServer) generateUserCertBundle(auth ClientI, w http.ResponseWriter,
}, nil
}

type generateTokenReq struct {
Roles teleport.Roles `json:"roles"`
TTL time.Duration `json:"ttl"`
}

func (s *APIServer) generateToken(auth ClientI, w http.ResponseWriter, r *http.Request, _ httprouter.Params, version string) (interface{}, error) {
var req *generateTokenReq
var req GenerateTokenRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
token, err := auth.GenerateToken(req.Roles, req.TTL)
token, err := auth.GenerateToken(req)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
38 changes: 30 additions & 8 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -557,20 +557,42 @@ func (s *AuthServer) CreateWebSession(user string) (services.WebSession, error)
return sess, nil
}

func (s *AuthServer) GenerateToken(roles teleport.Roles, ttl time.Duration) (string, error) {
for _, role := range roles {
// GenerateTokenRequest is a request to generate auth token
type GenerateTokenRequest struct {
// Token if provided sets the token value, otherwise will be auto generated
Token string `json:"token"`
// Roles is a list of roles this token authenticates as
Roles teleport.Roles `json:"roles"`
// TTL is a time to live for token
TTL time.Duration `json:"ttl"`
}

// CheckAndSetDefaults checks and sets default values of request
func (req *GenerateTokenRequest) CheckAndSetDefaults() error {
for _, role := range req.Roles {
if err := role.Check(); err != nil {
return "", trace.Wrap(err)
return trace.Wrap(err)
}
}
token, err := utils.CryptoRandomHex(TokenLenBytes)
if err != nil {
if req.Token == "" {
token, err := utils.CryptoRandomHex(TokenLenBytes)
if err != nil {
return trace.Wrap(err)
}
req.Token = token
}
return nil
}

// GenerateToken generates multi-purpose authentication token
func (s *AuthServer) GenerateToken(req GenerateTokenRequest) (string, error) {
if err := req.CheckAndSetDefaults(); err != nil {
return "", trace.Wrap(err)
}
if err := s.Provisioner.UpsertToken(token, roles, ttl); err != nil {
return "", err
if err := s.Provisioner.UpsertToken(req.Token, req.Roles, req.TTL); err != nil {
return "", trace.Wrap(err)
}
return token, nil
return req.Token, nil
}

// ClientCertPool returns trusted x509 cerificate authority pool
Expand Down
22 changes: 18 additions & 4 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ func (s *AuthSuite) TestTokensCRUD(c *C) {
c.Assert(err, IsNil)
c.Assert(len(btokens), Equals, 0)

// generate single-use token (TTL is 0)
tok, err := s.a.GenerateToken(teleport.Roles{teleport.RoleNode}, 0)
// generate persistent token
tok, err := s.a.GenerateToken(GenerateTokenRequest{Roles: teleport.Roles{teleport.RoleNode}})
c.Assert(err, IsNil)
c.Assert(len(tok), Equals, 2*TokenLenBytes)

Expand Down Expand Up @@ -198,8 +198,22 @@ func (s *AuthSuite) TestTokensCRUD(c *C) {
roles, err = s.a.ValidateToken(tok)
c.Assert(err, IsNil)

// generate predefined token
customToken := "custom token"
tok, err = s.a.GenerateToken(GenerateTokenRequest{Roles: teleport.Roles{teleport.RoleNode}, Token: customToken})
c.Assert(err, IsNil)
c.Assert(tok, Equals, customToken)

roles, err = s.a.ValidateToken(tok)
c.Assert(err, IsNil)
c.Assert(roles.Include(teleport.RoleNode), Equals, true)
c.Assert(roles.Include(teleport.RoleProxy), Equals, false)

err = s.a.DeleteToken(customToken)
c.Assert(err, IsNil)

// generate multi-use token with long TTL:
multiUseToken, err := s.a.GenerateToken(teleport.Roles{teleport.RoleProxy}, time.Hour)
multiUseToken, err := s.a.GenerateToken(GenerateTokenRequest{Roles: teleport.Roles{teleport.RoleProxy}, TTL: time.Hour})
c.Assert(err, IsNil)
_, err = s.a.ValidateToken(multiUseToken)
c.Assert(err, IsNil)
Expand Down Expand Up @@ -285,7 +299,7 @@ func (s *AuthSuite) TestBadTokens(c *C) {
c.Assert(err, NotNil)

// tampered
tok, err := s.a.GenerateToken(teleport.Roles{teleport.RoleAuth}, 0)
tok, err := s.a.GenerateToken(GenerateTokenRequest{Roles: teleport.Roles{teleport.RoleAuth}})
c.Assert(err, IsNil)

tampered := string(tok[0]+1) + tok[1:]
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,11 @@ func (a *AuthWithRoles) DeactivateCertAuthority(id services.CertAuthID) error {
return trace.BadParameter("not implemented")
}

func (a *AuthWithRoles) GenerateToken(roles teleport.Roles, ttl time.Duration) (string, error) {
func (a *AuthWithRoles) GenerateToken(req GenerateTokenRequest) (string, error) {
if err := a.action(defaults.Namespace, services.KindToken, services.VerbCreate); err != nil {
return "", trace.Wrap(err)
}
return a.authServer.GenerateToken(roles, ttl)
return a.authServer.GenerateToken(req)
}

func (a *AuthWithRoles) RegisterUsingToken(req RegisterUsingTokenRequest) (*PackedKeys, error) {
Expand Down
15 changes: 7 additions & 8 deletions lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,10 @@ func (c *Client) DeactivateCertAuthority(id services.CertAuthID) error {
// This token is used by SSH server to authenticate with Auth server
// and get signed certificate and private key from the auth server.
//
// The token can be used only once.
func (c *Client) GenerateToken(roles teleport.Roles, ttl time.Duration) (string, error) {
out, err := c.PostJSON(c.Endpoint("tokens"), generateTokenReq{
Roles: roles,
TTL: ttl,
})
// If token is not supplied, it will be auto generated and returned.
// If TTL is not supplied, token will be valid until removed.
func (c *Client) GenerateToken(req GenerateTokenRequest) (string, error) {
out, err := c.PostJSON(c.Endpoint("tokens"), req)
if err != nil {
return "", trace.Wrap(err)
}
Expand Down Expand Up @@ -2205,8 +2203,9 @@ type IdentityService interface {
// This token is used by SSH server to authenticate with Auth server
// and get signed certificate and private key from the auth server.
//
// The token can be used only once.
GenerateToken(roles teleport.Roles, ttl time.Duration) (string, error)
// If token is not supplied, it will be auto generated and returned.
// If TTL is not supplied, token will be valid until removed.
GenerateToken(GenerateTokenRequest) (string, error)

// GenerateKeyPair generates SSH private/public key pair optionally protected
// by password. If the pass parameter is an empty string, the key pair
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func (s *TLSSuite) TestTokens(c *check.C) {
clt, err := s.server.NewClient(TestAdmin())
c.Assert(err, check.IsNil)

out, err := clt.GenerateToken(teleport.Roles{teleport.RoleNode}, 0)
out, err := clt.GenerateToken(GenerateTokenRequest{Roles: teleport.Roles{teleport.RoleNode}})
c.Assert(err, check.IsNil)
c.Assert(len(out), check.Not(check.Equals), 0)
}
Expand Down
83 changes: 59 additions & 24 deletions tool/tctl/common/node_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ import (
// NodeCommand implements `tctl nodes` group of commands
type NodeCommand struct {
config *service.Config
// count is optional hidden field that will cause
// tctl issue count tokens and output them in JSON format
count int
// format is the output format, e.g. text or json
format string
// list of roles for the new node to assume
Expand All @@ -46,6 +43,9 @@ type NodeCommand struct {
ttl time.Duration
// namespace is node namespace
namespace string
// token is an optional custom token supplied by client,
// if not specified, is autogenerated
token string

// CLI subcommands (clauses)
nodeAdd *kingpin.CmdClause
Expand All @@ -61,8 +61,8 @@ func (c *NodeCommand) Initialize(app *kingpin.Application, config *service.Confi
c.nodeAdd = nodes.Command("add", "Generate a node invitation token")
c.nodeAdd.Flag("roles", "Comma-separated list of roles for the new node to assume [node]").Default("node").StringVar(&c.roles)
c.nodeAdd.Flag("ttl", "Time to live for a generated token").Default(defaults.ProvisioningTokenTTL.String()).DurationVar(&c.ttl)
c.nodeAdd.Flag("count", "add count tokens and output JSON with the list").Hidden().Default("1").IntVar(&c.count)
c.nodeAdd.Flag("format", "output format, 'text' or 'json'").Hidden().Default("text").StringVar(&c.format)
c.nodeAdd.Flag("token", "Custom token to use, autogenerated if not provided").StringVar(&c.token)
c.nodeAdd.Flag("format", "Output format, 'text' or 'json'").Hidden().Default("text").StringVar(&c.format)
c.nodeAdd.Alias(AddNodeHelp)

c.nodeList = nodes.Command("ls", "List all active SSH nodes within the cluster")
Expand All @@ -84,47 +84,82 @@ func (c *NodeCommand) TryRun(cmd string, client auth.ClientI) (match bool, err e
return true, trace.Wrap(err)
}

const trustedClusterTemplate = `kind: trusted_cluster
version: v2
metadata:
name: %v
spec:
enabled: true
token: %v
web_proxy_addr: proxy.example.com:3080
role_map:
- remote: admin
local: [admin]`

const trustedClusterMessage = `Trusted cluster token: %v
Use this cluster in trusted cluster resource, for example:
%v
Please note:
- This token will expire in %d minutes.
- Replace address proxy.example.com:3080 with externally accessible teleport proxy address.
- Set proper local and remote role_map property.
`

const nodeMessage = `The invite token: %v
Run this on the new node to join the cluster:
> teleport start --roles=%s --token=%v --auth-server=%v
Please note:
- This invitation token will expire in %d minutes
- %v must be reachable from the new node, see --advertise-ip server flag
`

// Invite generates a token which can be used to add another SSH node
// to a cluster
func (c *NodeCommand) Invite(client auth.ClientI) error {
if c.count < 1 {
return trace.BadParameter("count should be > 0, got %v", c.count)
}
// parse --roles flag
roles, err := teleport.ParseRoles(c.roles)
if err != nil {
return trace.Wrap(err)
}
var tokens []string
for i := 0; i < c.count; i++ {
token, err := client.GenerateToken(roles, c.ttl)
if err != nil {
return trace.Wrap(err)
}
tokens = append(tokens, token)
token, err := client.GenerateToken(auth.GenerateTokenRequest{Roles: roles, TTL: c.ttl, Token: c.token})
if err != nil {
return trace.Wrap(err)
}

authServers, err := client.GetAuthServers()
if err != nil {
return trace.Wrap(err)
}
if len(authServers) == 0 {
return trace.Errorf("This cluster does not have any auth servers running")
return trace.Errorf("This cluster does not have any auth servers running.")
}

clusterName, err := client.GetClusterName()
if err != nil {
return trace.Wrap(err)
}

// output format swtich:
if c.format == "text" {
for _, token := range tokens {
fmt.Printf(
"The invite token: %v\nRun this on the new node to join the cluster:\n> teleport start --roles=%s --token=%v --auth-server=%v\n\nPlease note:\n",
token, strings.ToLower(roles.String()), token, authServers[0].GetAddr())
if roles.Include(teleport.RoleTrustedCluster) {
trustedCluster := fmt.Sprintf(trustedClusterTemplate, clusterName.GetClusterName(), token)
fmt.Printf(trustedClusterMessage, token, trustedCluster, int(c.ttl.Minutes()))
} else {
fmt.Printf(nodeMessage,
token, strings.ToLower(roles.String()), token, authServers[0].GetAddr(), int(c.ttl.Minutes()), authServers[0].GetAddr())
}
fmt.Printf(" - This invitation token will expire in %d minutes\n", int(c.ttl.Minutes()))
fmt.Printf(" - %v must be reachable from the new node, see --advertise-ip server flag\n", authServers[0].GetAddr())
} else {
out, err := json.Marshal(tokens)
out, err := json.Marshal(token)
if err != nil {
return trace.Wrap(err, "failed to marshal tokens")
return trace.Wrap(err, "failed to marshal token")
}
fmt.Printf(string(out))
}
Expand Down

0 comments on commit 15de8a5

Please sign in to comment.