Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moved expires to resource metadata for services.Users. #2564

Merged
merged 3 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 139 additions & 50 deletions lib/auth/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,55 +150,48 @@ func (s *AuthServer) validateGithubAuthCallback(q url.Values) (*GithubAuthRespon
if err != nil {
return nil, trace.Wrap(err)
}
err = s.createGithubUser(connector, *claims)

// Calculate (figure out name, roles, traits, session TTL) of user and
// create the user in the backend.
params, err := s.calculateGithubUser(connector, claims, req)
if err != nil {
return nil, trace.Wrap(err)
}
user, err := s.createGithubUser(params)
if err != nil {
return nil, trace.Wrap(err)
}

// Auth was successful, return session, certificate, etc. to caller.
response := &GithubAuthResponse{
Req: *req,
Identity: services.ExternalIdentity{
ConnectorID: connector.GetName(),
Username: claims.Username,
ConnectorID: params.connectorName,
Username: params.username,
},
Req: *req,
}
user, err := s.Identity.GetUserByGithubIdentity(response.Identity)
if err != nil {
return nil, trace.Wrap(err)
Username: user.GetName(),
}
response.Username = user.GetName()
roles, err := services.FetchRoles(user.GetRoles(), s.Access, user.GetTraits())
if err != nil {
return nil, trace.Wrap(err)
}

// If the request is coming from a browser, create a web session.
if req.CreateWebSession {
session, err := s.NewWebSession(user.GetName())
if err != nil {
return nil, trace.Wrap(err)
}
sessionTTL := roles.AdjustSessionTTL(defaults.OAuth2TTL)
bearerTTL := utils.MinTTL(BearerTokenTTL, sessionTTL)
session.SetExpiryTime(s.clock.Now().UTC().Add(sessionTTL))
session.SetBearerTokenExpiryTime(s.clock.Now().UTC().Add(bearerTTL))
err = s.UpsertWebSession(user.GetName(), session)
session, err := s.createWebSession(user, params.sessionTTL)
if err != nil {
return nil, trace.Wrap(err)
}

response.Session = session
}

// If a public key was provided, sign it and return a certificate.
if len(req.PublicKey) != 0 {
certTTL := utils.MinTTL(defaults.OAuth2TTL, req.CertTTL)
certs, err := s.generateUserCert(certRequest{
user: user,
roles: roles,
ttl: certTTL,
publicKey: req.PublicKey,
compatibility: req.Compatibility,
})
sshCert, tlsCert, err := s.createSessionCert(user, params.sessionTTL, req.PublicKey, req.Compatibility)
if err != nil {
return nil, trace.Wrap(err)
}
response.Cert = certs.ssh
response.TLSCert = certs.tls

response.Cert = sshCert
response.TLSCert = tlsCert

// Return the host CA for this cluster only.
authority, err := s.GetCertAuthority(services.CertAuthID{
Expand All @@ -210,61 +203,157 @@ func (s *AuthServer) validateGithubAuthCallback(q url.Values) (*GithubAuthRespon
}
response.HostSigners = append(response.HostSigners, authority)
}

return response, nil
}

func (s *AuthServer) createGithubUser(connector services.GithubConnector, claims services.GithubClaims) error {
logins, kubeGroups := connector.MapClaims(claims)
if len(logins) == 0 {
return trace.BadParameter(
func (s *AuthServer) createWebSession(user services.User, sessionTTL time.Duration) (services.WebSession, error) {
session, err := s.NewWebSession(user.GetName())
if err != nil {
return nil, trace.Wrap(err)
}

// Session expiry time is the same as the user expiry time.
session.SetExpiryTime(s.clock.Now().UTC().Add(sessionTTL))

// Bearer tokens expire quicker than the overall session time and need to be refreshed.
bearerTTL := utils.MinTTL(BearerTokenTTL, sessionTTL)
session.SetBearerTokenExpiryTime(s.clock.Now().UTC().Add(bearerTTL))

err = s.UpsertWebSession(user.GetName(), session)
if err != nil {
return nil, trace.Wrap(err)
}

return session, nil
}

func (s *AuthServer) createSessionCert(user services.User, sessionTTL time.Duration, publicKey []byte, compatibility string) ([]byte, []byte, error) {
roles, err := services.FetchRoles(user.GetRoles(), s.Access, user.GetTraits())
if err != nil {
return nil, nil, trace.Wrap(err)
}

certs, err := s.generateUserCert(certRequest{
user: user,
roles: roles,
ttl: sessionTTL,
publicKey: publicKey,
compatibility: compatibility,
})
if err != nil {
return nil, nil, trace.Wrap(err)
}

return certs.ssh, certs.tls, nil
}

// createUserParams is a set of parameters used to create a user for an
// external identity provider.
type createUserParams struct {
// connectorName is the name of the connector for the identity provider.
connectorName string

// username is the Teleport user name .
username string

// logins is the list of *nix logins.
logins []string

// kubeGroups is the list of Kubernetes this user belongs to.
kubeGroups []string

// roles is the list of roles this user is assigned to.
roles []string

// traits is the list of traits for this user.
traits map[string][]string

// sessionTTL is how long this session will last.
sessionTTL time.Duration
}

func (s *AuthServer) calculateGithubUser(connector services.GithubConnector, claims *services.GithubClaims, request *services.GithubAuthRequest) (*createUserParams, error) {
p := createUserParams{
connectorName: connector.GetName(),
username: claims.Username,
}

// Calculate logins, kubegroups, roles, and traits.
p.logins, p.kubeGroups = connector.MapClaims(*claims)
if len(p.logins) == 0 {
return nil, trace.BadParameter(
"user %q does not belong to any teams configured in %q connector",
claims.Username, connector.GetName())
}
p.roles = modules.GetModules().RolesFromLogins(p.logins)
p.traits = modules.GetModules().TraitsFromLogins(p.logins, p.kubeGroups)

// Pick smaller for role: session TTL from role or requested TTL.
roles, err := services.FetchRoles(p.roles, s.Access, p.traits)
if err != nil {
return nil, trace.Wrap(err)
}
roleTTL := roles.AdjustSessionTTL(defaults.MaxCertDuration)
p.sessionTTL = utils.MinTTL(roleTTL, request.CertTTL)

return &p, nil
}

func (s *AuthServer) createGithubUser(p *createUserParams) (services.User, error) {

log.WithFields(logrus.Fields{trace.Component: "github"}).Debugf(
"Generating dynamic identity %v/%v with logins: %v.",
connector.GetName(), claims.Username, logins)
p.connectorName, p.username, p.logins)

expires := s.GetClock().Now().UTC().Add(p.sessionTTL)

user, err := services.GetUserMarshaler().GenerateUser(&services.UserV2{
Kind: services.KindUser,
Version: services.V2,
Metadata: services.Metadata{
Name: claims.Username,
Name: p.username,
Namespace: defaults.Namespace,
Expires: &expires,
},
Spec: services.UserSpecV2{
Roles: modules.GetModules().RolesFromLogins(logins),
Traits: modules.GetModules().TraitsFromLogins(logins, kubeGroups),
Expires: s.clock.Now().UTC().Add(defaults.OAuth2TTL),
Roles: p.roles,
Traits: p.traits,
GithubIdentities: []services.ExternalIdentity{{
ConnectorID: connector.GetName(),
Username: claims.Username,
ConnectorID: p.connectorName,
Username: p.username,
}},
CreatedBy: services.CreatedBy{
User: services.UserRef{Name: "system"},
Time: time.Now().UTC(),
Time: s.GetClock().Now().UTC(),
Connector: &services.ConnectorRef{
Type: teleport.ConnectorGithub,
ID: connector.GetName(),
Identity: claims.Username,
ID: p.connectorName,
Identity: p.username,
},
},
},
})
existingUser, err := s.GetUser(claims.Username)
if err != nil {
return nil, trace.Wrap(err)
}

existingUser, err := s.GetUser(p.username)
if err != nil && !trace.IsNotFound(err) {
return trace.Wrap(err)
return nil, trace.Wrap(err)
}
if existingUser != nil {
ref := user.GetCreatedBy().Connector
if !ref.IsSameProvider(existingUser.GetCreatedBy().Connector) {
return trace.AlreadyExists("user %q already exists and is not Github user",
return nil, trace.AlreadyExists("user %q already exists and is not Github user",
existingUser.GetName())
}
}
err = s.UpsertUser(user)
if err != nil {
return trace.Wrap(err)
return nil, trace.Wrap(err)
}
return nil
return user, nil
}

// populateGithubClaims retrieves information about user and its team
Expand Down
63 changes: 61 additions & 2 deletions lib/auth/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,56 @@ limitations under the License.
package auth

import (
"context"
"fmt"
"time"

authority "github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"

check "gopkg.in/check.v1"
"github.com/jonboulle/clockwork"
"gopkg.in/check.v1"
)

type GithubSuite struct{}
type GithubSuite struct {
a *AuthServer
b backend.Backend
c clockwork.FakeClock
}

var _ = fmt.Printf
var _ = check.Suite(&GithubSuite{})

func (s *GithubSuite) SetUpSuite(c *check.C) {
var err error

utils.InitLoggerForTests()

s.c = clockwork.NewFakeClockAt(time.Now())

s.b, err = lite.NewWithConfig(context.Background(), lite.Config{
Path: c.MkDir(),
PollStreamPeriod: 200 * time.Millisecond,
Clock: s.c,
})
c.Assert(err, check.IsNil)

clusterName, err := services.NewClusterName(services.ClusterNameSpecV2{
ClusterName: "me.localhost",
})
c.Assert(err, check.IsNil)

authConfig := &InitConfig{
ClusterName: clusterName,
Backend: s.b,
Authority: authority.New(),
SkipPeriodicOperations: true,
}
s.a, err = NewAuthServer(authConfig)
c.Assert(err, check.IsNil)
}

func (s *GithubSuite) TestPopulateClaims(c *check.C) {
Expand All @@ -43,6 +81,27 @@ func (s *GithubSuite) TestPopulateClaims(c *check.C) {
})
}

func (s *GithubSuite) TestCreateGithubUser(c *check.C) {
// Create GitHub user with 1 minute expiry.
_, err := s.a.createGithubUser(&createUserParams{
connectorName: "github",
username: "foo",
logins: []string{"foo"},
roles: []string{"admin"},
sessionTTL: 1 * time.Minute,
})
c.Assert(err, check.IsNil)

// Within that 1 minute period the user should still exist.
_, err = s.a.GetUser("foo")
c.Assert(err, check.IsNil)

// Advance time 2 minutes, the user should be gone.
s.c.Advance(2 * time.Minute)
_, err = s.a.GetUser("foo")
c.Assert(err, check.NotNil)
}

type testGithubAPIClient struct{}

func (c *testGithubAPIClient) getUser() (*userResponse, error) {
Expand Down
11 changes: 10 additions & 1 deletion lib/auth/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type TestAuthServerConfig struct {
AcceptedUsage []string
// CipherSuites is the list of ciphers that the server supports.
CipherSuites []uint16
// Clock is used to control time in tests.
Clock clockwork.FakeClock
}

// CheckAndSetDefaults checks and sets defaults
Expand All @@ -63,6 +65,9 @@ func (cfg *TestAuthServerConfig) CheckAndSetDefaults() error {
if cfg.Dir == "" {
return trace.BadParameter("missing parameter Dir")
}
if cfg.Clock == nil {
cfg.Clock = clockwork.NewFakeClockAt(time.Now())
}
if len(cfg.CipherSuites) == 0 {
cfg.CipherSuites = utils.DefaultCipherSuites()
}
Expand Down Expand Up @@ -107,7 +112,11 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
TestAuthServerConfig: cfg,
}
var err error
srv.Backend, err = lite.NewWithConfig(context.TODO(), lite.Config{Path: cfg.Dir, PollStreamPeriod: 100 * time.Millisecond})
srv.Backend, err = lite.NewWithConfig(context.Background(), lite.Config{
Path: cfg.Dir,
PollStreamPeriod: 100 * time.Millisecond,
Clock: cfg.Clock,
})
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
Loading