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

Dynamic Roles #897

Merged
merged 4 commits into from
Apr 5, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
}
role := services.RoleForUser(teleUser)
role.SetLogins(user.AllowedLogins)
err = auth.UpsertRole(role)
err = auth.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
}
Expand Down
3 changes: 2 additions & 1 deletion lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"time"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/services"
Expand Down Expand Up @@ -1308,7 +1309,7 @@ func (s *APIServer) upsertRole(auth ClientI, w http.ResponseWriter, r *http.Requ
if err != nil {
return nil, trace.Wrap(err)
}
err = auth.UpsertRole(role)
err = auth.UpsertRole(role, backend.Forever)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
6 changes: 3 additions & 3 deletions lib/auth/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (s *APISuite) TearDownTest(c *C) {
}

type clt interface {
UpsertRole(services.Role) error
UpsertRole(services.Role, time.Duration) error
UpsertUser(services.User) error
}

Expand All @@ -130,7 +130,7 @@ func createUserAndRole(clt clt, username string, allowedLogins []string) (servic
}
role := services.RoleForUser(user)
role.SetLogins([]string{user.GetName()})
err = clt.UpsertRole(role)
err = clt.UpsertRole(role, backend.Forever)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -221,7 +221,7 @@ func (s *APISuite) TestGenerateKeysAndCerts(c *C) {

// now update role to permit agent forwarding
userRole.SetForwardAgent(true)
err = s.clt.UpsertRole(userRole)
err = s.clt.UpsertRole(userRole, backend.Forever)
c.Assert(err, IsNil)

authorizer, err = NewUserAuthorizer("user1", s.WebS, s.AccessS)
Expand Down
62 changes: 47 additions & 15 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -779,13 +778,41 @@ type OIDCAuthResponse struct {
HostSigners []services.CertAuthority `json:"host_signers"`
}

func (a *AuthServer) createOIDCUser(connector services.OIDCConnector, ident *oidc.Identity, claims jose.Claims) error {
// buildRoles takes a connector and claims and returns a slice of roles. If the claims
// match a concrete roles in the connector, those roles are returned directly. If the
// claims match a template role in the connector, then that role is first created from
// the template, then returned.
func (a *AuthServer) buildRoles(connector services.OIDCConnector, ident *oidc.Identity, claims jose.Claims) ([]string, error) {
roles := connector.MapClaims(claims)
if len(roles) == 0 {
log.Warningf("[OIDC] could not find any of expected claims: %v in the set returned by provider %v: %v",
strings.Join(connector.GetClaims(), ","), connector.GetName(), strings.Join(services.GetClaimNames(claims), ","))
return trace.AccessDenied("access denied to %v", ident.Email)
role, err := connector.RoleFromTemplate(claims)
if err != nil {
log.Warningf("[OIDC] Unable to map claims to roles or role templates for %q", connector.GetName())
return nil, trace.AccessDenied("unable to map claims to roles or role templates for %q", connector.GetName())
}

// figure out ttl for role. expires = now + ttl => ttl = expires - now
ttl := ident.ExpiresAt.Sub(time.Now())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use auth server clock instead?


// upsert templated role
err = a.Access.UpsertRole(role, ttl)
if err != nil {
log.Warningf("[OIDC] Unable to upsert templated role for connector: %q", connector.GetName())
return nil, trace.AccessDenied("unable to upsert templated role: %q", connector.GetName())
}

roles = []string{role.GetName()}
}

return roles, nil
}

func (a *AuthServer) createOIDCUser(connector services.OIDCConnector, ident *oidc.Identity, claims jose.Claims) error {
roles, err := a.buildRoles(connector, ident, claims)
if err != nil {
return trace.Wrap(err)
}

log.Debugf("[IDENTITY] %v/%v is a dynamic identity, generating user with roles: %v", connector.GetName(), ident.Email, roles)
user, err := services.GetUserMarshaler().GenerateUser(&services.UserV2{
Kind: services.KindUser,
Expand All @@ -812,25 +839,30 @@ func (a *AuthServer) createOIDCUser(connector services.OIDCConnector, ident *oid
if err != nil {
return trace.Wrap(err)
}
err = a.CreateUser(user)
if err == nil {
return trace.Wrap(err)
}
if !trace.IsAlreadyExists(err) {
return trace.Wrap(err)
}

// check if a user exists already
existingUser, err := a.GetUser(ident.Email)
if err != nil {
if !trace.IsNotFound(err) {
return trace.Wrap(err)
}
} else {
}

// check if any exisiting user is a non-oidc user, dont override their
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incomplete comment sentence

if existingUser != nil {
connectorRef := existingUser.GetCreatedBy().Connector
if connectorRef == nil || connectorRef.Type != teleport.ConnectorOIDC || connectorRef.ID != connector.GetName() {
return trace.AlreadyExists("user %v already exists and is not OIDC user", existingUser.GetName())
return trace.AlreadyExists("user %q already exists and is not OIDC user", existingUser.GetName())
}
}
return a.UpsertUser(user)

// no non-oidc user exists, create or update the exisiting oidc user
err = a.UpsertUser(user)
if err != nil {
return trace.Wrap(err)
}

return nil
}

// claimsFromIDToken extracts claims from the ID token.
Expand Down
128 changes: 127 additions & 1 deletion lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package auth

import (
"fmt"
"testing"
"time"

Expand All @@ -28,9 +29,12 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/suite"
"github.com/gravitational/teleport/lib/utils"
"github.com/jonboulle/clockwork"

"github.com/gravitational/trace"

"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/oidc"
"github.com/jonboulle/clockwork"
. "gopkg.in/check.v1"
)

Expand All @@ -42,6 +46,7 @@ type AuthSuite struct {
}

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

func (s *AuthSuite) SetUpSuite(c *C) {
utils.InitLoggerForTests()
Expand Down Expand Up @@ -212,3 +217,124 @@ func (s *AuthSuite) TestBadTokens(c *C) {
_, err = s.a.ValidateToken(tampered)
c.Assert(err, NotNil)
}

func (s *AuthSuite) TestBuildRolesInvalid(c *C) {
// create a connector
oidcConnector := services.NewOIDCConnector("example", services.OIDCConnectorSpecV2{
IssuerURL: "https://www.exmaple.com",
ClientID: "example-client-id",
ClientSecret: "example-client-secret",
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
Display: "sign in with example.com",
Scope: []string{"foo", "bar"},
})

// create some claims
var claims = make(jose.Claims)
claims.Add("roles", "teleport-user")
claims.Add("email", "[email protected]")
claims.Add("nickname", "foo")
claims.Add("full_name", "foo bar")

// create an identity for the ttl
ident := &oidc.Identity{
ExpiresAt: time.Now().Add(1 * time.Minute),
}

// try and build roles should be invalid since we have no mappings
_, err := s.a.buildRoles(oidcConnector, ident, claims)
c.Assert(err, NotNil)
}

func (s *AuthSuite) TestBuildRolesStatic(c *C) {
// create a connector
oidcConnector := services.NewOIDCConnector("example", services.OIDCConnectorSpecV2{
IssuerURL: "https://www.exmaple.com",
ClientID: "example-client-id",
ClientSecret: "example-client-secret",
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
Display: "sign in with example.com",
Scope: []string{"foo", "bar"},
ClaimsToRoles: []services.ClaimMapping{
services.ClaimMapping{
Claim: "roles",
Value: "teleport-user",
Roles: []string{"user"},
},
},
})

// create some claims
var claims = make(jose.Claims)
claims.Add("roles", "teleport-user")
claims.Add("email", "[email protected]")
claims.Add("nickname", "foo")
claims.Add("full_name", "foo bar")

// create an identity for the ttl
ident := &oidc.Identity{
ExpiresAt: time.Now().Add(1 * time.Minute),
}

// build roles and check that we mapped to "user" role
roles, err := s.a.buildRoles(oidcConnector, ident, claims)
c.Assert(err, IsNil)
c.Assert(roles, HasLen, 1)
c.Assert(roles[0], Equals, "user")
}

func (s *AuthSuite) TestBuildRolesTemplate(c *C) {
// create a connector
oidcConnector := services.NewOIDCConnector("example", services.OIDCConnectorSpecV2{
IssuerURL: "https://www.exmaple.com",
ClientID: "example-client-id",
ClientSecret: "example-client-secret",
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
Display: "sign in with example.com",
Scope: []string{"foo", "bar"},
ClaimsToRoles: []services.ClaimMapping{
services.ClaimMapping{
Claim: "roles",
Value: "teleport-user",
RoleTemplate: &services.RoleV2{
Kind: services.KindRole,
Version: services.V2,
Metadata: services.Metadata{
Name: `{{index . "email"}}`,
Namespace: defaults.Namespace,
},
Spec: services.RoleSpecV2{
MaxSessionTTL: services.NewDuration(90 * 60 * time.Minute),
Logins: []string{`{{index . "nickname"}}`, `root`},
NodeLabels: map[string]string{"*": "*"},
Namespaces: []string{"*"},
},
},
},
},
})

// create some claims
var claims = make(jose.Claims)
claims.Add("roles", "teleport-user")
claims.Add("email", "[email protected]")
claims.Add("nickname", "foo")
claims.Add("full_name", "foo bar")

// create an identity for the ttl
ident := &oidc.Identity{
ExpiresAt: time.Now().Add(1 * time.Minute),
}

// build roles
roles, err := s.a.buildRoles(oidcConnector, ident, claims)
c.Assert(err, IsNil)

// check that the newly created role was both returned and upserted into the backend
r, err := s.a.GetRoles()
c.Assert(err, IsNil)
c.Assert(r, HasLen, 1)
c.Assert(r[0].GetName(), Equals, "[email protected]")
c.Assert(roles, HasLen, 1)
c.Assert(roles[0], Equals, "[email protected]")
}
4 changes: 2 additions & 2 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,11 @@ func (a *AuthWithRoles) GetRoles() ([]services.Role, error) {
}

// UpsertRole creates or updates role
func (a *AuthWithRoles) UpsertRole(role services.Role) error {
func (a *AuthWithRoles) UpsertRole(role services.Role, ttl time.Duration) error {
if err := a.action(defaults.Namespace, services.KindRole, services.ActionWrite); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertRole(role)
return a.authServer.UpsertRole(role, ttl)
}

// GetRole returns role by name
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ func (c *Client) GetRoles() ([]services.Role, error) {
}

// UpsertRole creates or updates role
func (c *Client) UpsertRole(role services.Role) error {
func (c *Client) UpsertRole(role services.Role, ttl time.Duration) error {
data, err := services.GetRoleMarshaler().MarshalRole(role)
if err != nil {
return trace.Wrap(err)
Expand Down
6 changes: 3 additions & 3 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func Init(cfg InitConfig, dynamicConfig bool) (*AuthServer, *Identity, error) {
}

for _, role := range cfg.Roles {
if err := asrv.UpsertRole(role); err != nil {
if err := asrv.UpsertRole(role, backend.Forever); err != nil {
return nil, nil, trace.Wrap(err)
}
log.Infof("[INIT] Created Role: %v", role)
Expand Down Expand Up @@ -330,7 +330,7 @@ func migrateUsers(asrv *AuthServer) error {
// create role for user and upsert to backend
role := services.RoleForUser(user)
role.SetLogins(raw.AllowedLogins)
err = asrv.UpsertRole(role)
err = asrv.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -370,7 +370,7 @@ func migrateCertAuthority(asrv *AuthServer) error {

// create role for certificate authority and upsert to backend
newCA, role := services.ConvertV1CertAuthority(&raw)
err = asrv.UpsertRole(role)
err = asrv.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
}
Expand Down
7 changes: 4 additions & 3 deletions lib/auth/new_web_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"bytes"
"image/png"

"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
Expand Down Expand Up @@ -188,7 +189,7 @@ func (s *AuthServer) CreateUserWithOTP(token string, password string, otpToken s
// apply user allowed logins
role := services.RoleForUser(tokenData.User.V2())
role.SetLogins(tokenData.User.AllowedLogins)
if err := s.UpsertRole(role); err != nil {
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}

Expand Down Expand Up @@ -234,7 +235,7 @@ func (s *AuthServer) CreateUserWithoutOTP(token string, password string) (servic
// apply user allowed logins
role := services.RoleForUser(tokenData.User.V2())
role.SetLogins(tokenData.User.AllowedLogins)
if err := s.UpsertRole(role); err != nil {
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}

Expand Down Expand Up @@ -304,7 +305,7 @@ func (s *AuthServer) CreateUserWithU2FToken(token string, password string, respo

role := services.RoleForUser(tokenData.User.V2())
role.SetLogins(tokenData.User.AllowedLogins)
if err := s.UpsertRole(role); err != nil {
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}

Expand Down
2 changes: 1 addition & 1 deletion lib/auth/tun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (s *TunSuite) TestUnixServerClient(c *C) {

user, role := createUserAndRole(s.a, userName, []string{userName})
role.SetResource(services.KindNode, services.RW())
err = s.a.UpsertRole(role)
err = s.a.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)

err = s.a.UpsertPassword(user.GetName(), pass)
Expand Down
Loading