Merge branch 'master' into alexey/http-headers

This commit is contained in:
Alexey Kontsevoy 2017-07-18 17:37:24 -04:00 committed by GitHub
commit c156e5a4ab
39 changed files with 1414 additions and 521 deletions

View file

@ -158,7 +158,7 @@ tctl create -f dev.yaml
### Login
For the Web UI, if the above configuration were real, you would see a button
that says `Login with adfs`. Simply click on that and you will be
that says `Login with Okta`. Simply click on that and you will be
re-directed to a login page for your identity provider and if successful,
redirected back to Teleport.
@ -167,6 +167,10 @@ and a browser window should automatically open taking you to the login page for
your identity provider. `tsh` will also output a link the login page of the
identity provider if you are not automatically redirected.
!!! note "IMPORTANT":
Teleport only supports sending party initiated flows for SAML 2.0. This
means you can not initiate login from your identity provider, you have to
initiate login from either the Teleport Web UI or CLI.
## ADFS

View file

@ -139,7 +139,7 @@ func (s *InstanceSecrets) GetRoles() []services.Role {
continue
}
role := services.RoleForCertAuthority(ca)
role.SetLogins(s.AllowedLogins())
role.SetLogins(services.Allow, s.AllowedLogins())
roles = append(roles, role)
}
return roles
@ -301,7 +301,7 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
var roles []services.Role
if len(user.Roles) == 0 {
role := services.RoleForUser(teleUser)
role.SetLogins(user.AllowedLogins)
role.SetLogins(services.Allow, user.AllowedLogins)
err = auth.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
@ -333,7 +333,7 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
}
// sign user's keys:
ttl := 24 * time.Hour
logins, err := services.RoleSet(roles).CheckLogins(ttl)
logins, err := services.RoleSet(roles).CheckLoginDuration(ttl)
if err != nil {
return trace.Wrap(err)
}

View file

@ -678,8 +678,10 @@ func (s *IntSuite) TestMapRoles(c *check.C) {
// main cluster has a local user and belongs to role "main-devs"
mainDevs := "main-devs"
role, err := services.NewRole(mainDevs, services.RoleSpecV2{
Logins: []string{username},
role, err := services.NewRole(mainDevs, services.RoleSpecV3{
Allow: services.RoleConditions{
Logins: []string{username},
},
})
c.Assert(err, check.IsNil)
main.AddUserWithRole(username, role)
@ -704,8 +706,10 @@ func (s *IntSuite) TestMapRoles(c *check.C) {
// using trusted clusters, so remote user will be allowed to assume
// role specified by mapping remote role "devs" to local role "local-devs"
auxDevs := "aux-devs"
role, err = services.NewRole(auxDevs, services.RoleSpecV2{
Logins: []string{username},
role, err = services.NewRole(auxDevs, services.RoleSpecV3{
Allow: services.RoleConditions{
Logins: []string{username},
},
})
c.Assert(err, check.IsNil)
err = aux.Process.GetAuthServer().UpsertRole(role, backend.Forever)

View file

@ -129,7 +129,7 @@ func createUserAndRole(clt clt, username string, allowedLogins []string) (servic
panic(err)
}
role := services.RoleForUser(user)
role.SetLogins([]string{user.GetName()})
role.SetLogins(services.Allow, []string{user.GetName()})
err = clt.UpsertRole(role, backend.Forever)
if err != nil {
panic(err)
@ -147,24 +147,28 @@ func createUserAndRoleWithoutRoles(clt clt, username string, allowedLogins []str
if err != nil {
panic(err)
}
role := services.RoleForUser(user)
role.RemoveResource(services.KindRole)
role.SetLogins([]string{user.GetName()})
resources := role.GetSystemResources(services.Allow)
delete(resources, services.KindRole)
role.SetSystemResources(services.Allow, resources)
role.SetLogins(services.Allow, []string{user.GetName()})
err = clt.UpsertRole(role, backend.Forever)
if err != nil {
panic(err)
}
user.AddRole(role.GetName())
err = clt.UpsertUser(user)
if err != nil {
panic(err)
}
return user, role
}
// TestOwnRole tests that user can read roles assigned to them
func (s *APISuite) TestReadOwnRole(c *C) {
user1, userRole := createUserAndRoleWithoutRoles(s.clt, "user1", []string{"user1"})
user2, _ := createUserAndRoleWithoutRoles(s.clt, "user2", []string{"user2"})
err := s.clt.UpsertPassword(user1.GetName(), []byte("abc1231"))
@ -269,7 +273,9 @@ func (s *APISuite) TestGenerateKeysAndCerts(c *C) {
c.Assert(exists, Equals, false)
// now update role to permit agent forwarding
userRole.SetForwardAgent(true)
roleOptions := userRole.GetOptions()
roleOptions.Set(services.ForwardAgent, true)
userRole.SetOptions(roleOptions)
err = s.clt.UpsertRole(userRole, backend.Forever)
c.Assert(err, IsNil)

View file

@ -653,7 +653,7 @@ func (s *AuthServer) NewWebSession(userName string) (services.WebSession, error)
sessionTTL := roles.AdjustSessionTTL(defaults.CertDuration)
bearerTokenTTL := utils.MinTTL(sessionTTL, BearerTokenTTL)
allowedLogins, err := roles.CheckLogins(sessionTTL)
allowedLogins, err := roles.CheckLoginDuration(sessionTTL)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -42,7 +42,7 @@ type AuthWithRoles struct {
}
func (a *AuthWithRoles) action(namespace string, resourceKind, action string) error {
return a.checker.CheckResourceAction(namespace, resourceKind, action)
return a.checker.CheckAccessToRuleOrResource(namespace, resourceKind, action)
}
// currentUserAction is a special checker that allows certain actions for users
@ -52,7 +52,7 @@ func (a *AuthWithRoles) currentUserAction(username string) error {
if username == a.user.GetName() {
return nil
}
return a.checker.CheckResourceAction(
return a.checker.CheckAccessToRuleOrResource(
defaults.Namespace, services.KindUser, services.ActionWrite)
}
@ -393,7 +393,7 @@ func (a *AuthWithRoles) GenerateUserCert(key []byte, username string, ttl time.D
sessionTTL := checker.AdjustSessionTTL(ttl)
// check signing TTL and return a list of allowed logins
allowedLogins, err := checker.CheckLogins(sessionTTL)
allowedLogins, err := checker.CheckLoginDuration(sessionTTL)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -310,6 +310,11 @@ func migrateLegacyResources(cfg InitConfig, asrv *AuthServer) error {
return trace.Wrap(err)
}
err = migrateRoles(asrv)
if err != nil {
return trace.Wrap(err)
}
return nil
}
@ -329,7 +334,7 @@ func migrateUsers(asrv *AuthServer) error {
// create role for user and upsert to backend
role := services.RoleForUser(user)
role.SetLogins(raw.AllowedLogins)
role.SetLogins(services.Allow, raw.AllowedLogins)
err = asrv.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
@ -418,6 +423,31 @@ func migrateAuthPreference(cfg InitConfig, asrv *AuthServer) error {
return nil
}
func migrateRoles(asrv *AuthServer) error {
roles, err := asrv.GetRoles()
if err != nil {
return trace.Wrap(err)
}
// loop over all roles and only migrate RoleV2 -> RoleV3
for i, _ := range roles {
role := roles[i]
_, ok := (role.GetRawObject()).(services.RoleV2)
if !ok {
continue
}
// with RoleV2 we never had a TTL so upsert them forever
err = asrv.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
}
log.Infof("[MIGRATION] Updated Role: %v", role.GetName())
}
return nil
}
// isFirstStart returns 'true' if the auth server is starting for the 1st time
// on this server.
func isFirstStart(authServer *AuthServer, cfg InitConfig) (bool, error) {

View file

@ -197,7 +197,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)
role.SetLogins(services.Allow, tokenData.User.AllowedLogins)
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}
@ -243,7 +243,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)
role.SetLogins(services.Allow, tokenData.User.AllowedLogins)
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}
@ -313,7 +313,7 @@ func (s *AuthServer) CreateUserWithU2FToken(token string, password string, respo
}
role := services.RoleForUser(tokenData.User.V2())
role.SetLogins(tokenData.User.AllowedLogins)
role.SetLogins(services.Allow, tokenData.User.AllowedLogins)
if err := s.UpsertRole(role, backend.Forever); err != nil {
return nil, trace.Wrap(err)
}

View file

@ -214,7 +214,7 @@ func (a *AuthServer) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse,
if len(req.PublicKey) != 0 {
certTTL := utils.MinTTL(utils.ToTTL(a.clock, ident.ExpiresAt), req.CertTTL)
allowedLogins, err := roles.CheckLogins(certTTL)
allowedLogins, err := roles.CheckLoginDuration(certTTL)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -149,97 +149,114 @@ func GetCheckerForBuiltinRole(role teleport.Role) (services.AccessChecker, error
case teleport.RoleAuth:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{services.Wildcard},
Resources: map[string][]string{
services.KindAuthServer: services.RW()},
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
SystemResources: map[string][]string{
services.KindAuthServer: services.RW(),
},
},
})
case teleport.RoleProvisionToken:
return services.FromSpec(role.String(), services.RoleSpecV2{})
return services.FromSpec(role.String(), services.RoleSpecV3{})
case teleport.RoleNode:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{services.Wildcard},
Resources: map[string][]string{
services.KindNode: services.RW(),
services.KindSession: services.RW(),
services.KindEvent: services.RW(),
services.KindProxy: services.RO(),
services.KindCertAuthority: services.RO(),
services.KindUser: services.RO(),
services.KindNamespace: services.RO(),
services.KindRole: services.RO(),
services.KindAuthServer: services.RO(),
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
SystemResources: map[string][]string{
services.KindNode: services.RW(),
services.KindSession: services.RW(),
services.KindEvent: services.RW(),
services.KindProxy: services.RO(),
services.KindCertAuthority: services.RO(),
services.KindUser: services.RO(),
services.KindNamespace: services.RO(),
services.KindRole: services.RO(),
services.KindAuthServer: services.RO(),
},
},
})
case teleport.RoleProxy:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{services.Wildcard},
Resources: map[string][]string{
services.KindProxy: services.RW(),
services.KindOIDCRequest: services.RW(),
services.KindOIDC: services.RO(),
services.KindSAMLRequest: services.RW(),
services.KindSAML: services.RO(),
services.KindNamespace: services.RO(),
services.KindEvent: services.RW(),
services.KindSession: services.RW(),
services.KindNode: services.RO(),
services.KindAuthServer: services.RO(),
services.KindReverseTunnel: services.RO(),
services.KindCertAuthority: services.RO(),
services.KindUser: services.RO(),
services.KindRole: services.RO(),
services.KindClusterAuthPreference: services.RO(),
services.KindUniversalSecondFactor: services.RO(),
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
SystemResources: map[string][]string{
services.KindProxy: services.RW(),
services.KindOIDCRequest: services.RW(),
services.KindSession: services.RW(),
services.KindEvent: services.RW(),
services.KindSAMLRequest: services.RW(),
services.KindOIDC: services.RO(),
services.KindSAML: services.RO(),
services.KindNamespace: services.RO(),
services.KindNode: services.RO(),
services.KindAuthServer: services.RO(),
services.KindReverseTunnel: services.RO(),
services.KindCertAuthority: services.RO(),
services.KindUser: services.RO(),
services.KindRole: services.RO(),
services.KindClusterAuthPreference: services.RO(),
services.KindUniversalSecondFactor: services.RO(),
},
},
})
case teleport.RoleWeb:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{services.Wildcard},
Resources: map[string][]string{
services.KindWebSession: services.RW(),
services.KindSession: services.RW(),
services.KindAuthServer: services.RO(),
services.KindUser: services.RO(),
services.KindRole: services.RO(),
services.KindNamespace: services.RO(),
services.KindTrustedCluster: services.RO(),
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
SystemResources: map[string][]string{
services.KindWebSession: services.RW(),
services.KindSession: services.RW(),
services.KindAuthServer: services.RO(),
services.KindUser: services.RO(),
services.KindRole: services.RO(),
services.KindNamespace: services.RO(),
services.KindTrustedCluster: services.RO(),
},
},
})
case teleport.RoleSignup:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{services.Wildcard},
Resources: map[string][]string{
services.KindAuthServer: services.RO(),
services.KindClusterAuthPreference: services.RO(),
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
SystemResources: map[string][]string{
services.KindAuthServer: services.RO(),
services.KindClusterAuthPreference: services.RO(),
},
},
})
case teleport.RoleAdmin:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
MaxSessionTTL: services.MaxDuration(),
Logins: []string{},
Namespaces: []string{services.Wildcard},
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
Resources: map[string][]string{
services.Wildcard: services.RW(),
services.RoleSpecV3{
Options: services.RoleOptions{
services.MaxSessionTTL: services.MaxDuration(),
},
Allow: services.RoleConditions{
Namespaces: []string{services.Wildcard},
Logins: []string{},
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
SystemResources: map[string][]string{
services.Wildcard: services.RW(),
},
},
})
case teleport.RoleNop:
return services.FromSpec(
role.String(),
services.RoleSpecV2{
Namespaces: []string{},
Resources: map[string][]string{},
services.RoleSpecV3{
Allow: services.RoleConditions{
Namespaces: []string{},
SystemResources: map[string][]string{},
},
})
}

View file

@ -189,9 +189,14 @@ func parseSAMLInResponseTo(response string) (string, error) {
return "", trace.BadParameter("unable to parse response")
}
// teleport only supports sending party initiated flows (Teleport sends an
// AuthnRequest to the IdP and gets a SAMLResponse from the IdP). identity
// provider initiated flows (where Teleport gets an unsolicited SAMLResponse
// from the IdP) are not supported.
el := doc.Root()
responseTo := el.SelectAttr("InResponseTo")
if responseTo == nil {
log.Errorf("[SAML] Teleport does not support initiating login from an identity provider, login must be initiated from either the Teleport Web UI or CLI.")
return "", trace.BadParameter("identity provider initiated flows are not supported")
}
if responseTo.Value == "" {
@ -311,7 +316,7 @@ func (a *AuthServer) ValidateSAMLResponse(samlResponse string) (*SAMLAuthRespons
if len(request.PublicKey) != 0 {
certTTL := utils.MinTTL(utils.ToTTL(a.clock, expiresAt), request.CertTTL)
allowedLogins, err := roles.CheckLogins(certTTL)
allowedLogins, err := roles.CheckLoginDuration(certTTL)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -138,7 +138,9 @@ func (s *TunSuite) TestUnixServerClient(c *C) {
otpSecret := base32.StdEncoding.EncodeToString([]byte(rawSecret))
user, role := createUserAndRole(s.a, userName, []string{userName})
role.SetResource(services.KindNode, services.RW())
resources := role.GetSystemResources(services.Allow)
resources[services.KindNode] = services.RW()
role.SetSystemResources(services.Allow, resources)
err = s.a.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)

View file

@ -474,7 +474,7 @@ func parseAuthorizedKeys(bytes []byte, allowedLogins []string) (services.CertAut
// transform old allowed logins into roles
role := services.RoleForCertAuthority(ca)
role.SetLogins(allowedLogins)
role.SetLogins(services.Allow, allowedLogins)
ca.AddRole(role.GetName())
return ca, role, nil

View file

@ -171,7 +171,7 @@ func GetAuthPreferenceSchema(extensionSchema string) string {
} else {
authPreferenceSchema = fmt.Sprintf(AuthPreferenceSpecSchemaTemplate, ","+extensionSchema)
}
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema, DefaultDefinitions)
}
// AuthPreferenceMarshaler implements marshal/unmarshal of AuthPreference implementations

View file

@ -542,7 +542,7 @@ type CertAuthorityMarshaler interface {
// GetCertAuthoritySchema returns JSON Schema for cert authorities
func GetCertAuthoritySchema() string {
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(CertAuthoritySpecV2Schema, RoleMapSchema))
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(CertAuthoritySpecV2Schema, RoleMapSchema), DefaultDefinitions)
}
type TeleportCertAuthorityMarshaler struct{}

View file

@ -382,7 +382,7 @@ func GetTrustedClusterSchema(extensionSchema string) string {
} else {
trustedClusterSchema = fmt.Sprintf(TrustedClusterSpecSchemaTemplate, RoleMapSchema, ","+extensionSchema)
}
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, trustedClusterSchema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, trustedClusterSchema, DefaultDefinitions)
}
// TrustedClusterMarshaler implements marshal/unmarshal of TrustedCluster implementations

View file

@ -240,3 +240,61 @@ func (s *MigrationsSuite) TestMigrateCertAuthorities(c *C) {
in.AllowedLogins = nil
c.Assert(out3, DeepEquals, *in)
}
func (s *MigrationsSuite) TestMigrateRoles(c *C) {
in := &RoleV2{
Kind: KindRole,
Version: V2,
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
MaxSessionTTL: NewDuration(20 * time.Hour),
Logins: []string{"foo"},
NodeLabels: map[string]string{"a": "b"},
Namespaces: []string{"system", "default"},
Resources: map[string][]string{"role": []string{"read", "write"}},
},
}
out := in.V3()
expected := &RoleV3{
Kind: KindRole,
Version: V3,
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV3{
Options: RoleOptions{
MaxSessionTTL: NewDuration(20 * time.Hour),
},
Allow: RoleConditions{
Logins: []string{"foo"},
NodeLabels: map[string]string{"a": "b"},
Namespaces: []string{"system", "default"},
SystemResources: map[string][]string{
"role": []string{ActionRead, ActionWrite},
},
},
},
rawObject: *in,
}
c.Assert(out.rawObject, DeepEquals, *in)
c.Assert(out, DeepEquals, expected)
data, err := json.Marshal(in)
c.Assert(err, IsNil)
out2, err := GetRoleMarshaler().UnmarshalRole(data)
c.Assert(err, IsNil)
c.Assert(out2, DeepEquals, out)
// check V3 marshaling
data, err = GetRoleMarshaler().MarshalRole(expected)
c.Assert(err, IsNil)
out3, err := GetRoleMarshaler().UnmarshalRole(data)
c.Assert(err, IsNil)
expected.rawObject = nil
c.Assert(out3, DeepEquals, expected)
}

View file

@ -451,7 +451,7 @@ func (o *OIDCConnectorV2) RoleFromTemplate(claims jose.Claims) (Role, error) {
return nil, trace.Wrap(err)
}
return roleTemplate, nil
return roleTemplate.V3(), nil
}
}
}
@ -602,7 +602,7 @@ var ClaimMappingSchema = fmt.Sprintf(`{
},
"role_template": %v
}
}`, GetRoleSchema(""))
}`, GetRoleSchema(V2, ""))
// OIDCConnectorV1 specifies configuration for Open ID Connect compatible external
// identity provider, e.g. google in some organisation

View file

@ -17,12 +17,9 @@ package services
import (
"fmt"
"time"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"
"github.com/coreos/go-oidc/jose"
"gopkg.in/check.v1"
)
@ -74,46 +71,14 @@ func (s *OIDCSuite) TestUnmarshal(c *check.C) {
}
`
output := OIDCConnectorV2{
Kind: KindOIDCConnector,
Version: V2,
Metadata: Metadata{
Name: "google",
Namespace: defaults.Namespace,
},
Spec: OIDCConnectorSpecV2{
IssuerURL: "https://accounts.google.com",
ClientID: "id-from-google.apps.googleusercontent.com",
ClientSecret: "secret-key-from-google",
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
Display: "whatever",
Scope: []string{"roles"},
ClaimsToRoles: []ClaimMapping{
ClaimMapping{
Claim: "roles",
Value: "teleport-user",
RoleTemplate: &RoleV2{
Kind: KindRole,
Version: V2,
Metadata: Metadata{
Name: `{{index . "email"}}`,
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
Namespaces: []string{"default"},
MaxSessionTTL: NewDuration(90 * 60 * time.Minute),
Logins: []string{`{{index . "nickname"}}`, `root`},
NodeLabels: map[string]string{"*": "*"},
},
},
},
},
},
}
oc, err := GetOIDCConnectorMarshaler().UnmarshalOIDCConnector([]byte(input))
c.Assert(err, check.IsNil)
c.Assert(oc, check.DeepEquals, &output)
c.Assert(oc.GetName(), check.Equals, "google")
c.Assert(oc.GetIssuerURL(), check.Equals, "https://accounts.google.com")
c.Assert(oc.GetClientID(), check.Equals, "id-from-google.apps.googleusercontent.com")
c.Assert(oc.GetRedirectURL(), check.Equals, "https://localhost:3080/v1/webapi/oidc/callback")
c.Assert(oc.GetDisplay(), check.Equals, "whatever")
}
func (s *OIDCSuite) TestUnmarshalInvalid(c *check.C) {
@ -161,64 +126,3 @@ func (s *OIDCSuite) TestUnmarshalInvalid(c *check.C) {
err = oc.Check()
c.Assert(err, check.NotNil)
}
// TestRoleFromTemplate checks that we can create a valid role from a template. Also
// makes sure missing fields are filled in.
func (s *OIDCSuite) TestRoleFromTemplate(c *check.C) {
oidcConnector := OIDCConnectorV2{
Kind: KindOIDCConnector,
Version: V2,
Metadata: Metadata{
Name: "google",
Namespace: defaults.Namespace,
},
Spec: OIDCConnectorSpecV2{
IssuerURL: "https://accounts.google.com",
ClientID: "id-from-google.apps.googleusercontent.com",
ClientSecret: "secret-key-from-google",
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
Display: "whatever",
Scope: []string{"roles"},
ClaimsToRoles: []ClaimMapping{
ClaimMapping{
Claim: "roles",
Value: "teleport-user",
RoleTemplate: &RoleV2{
Kind: KindRole,
Version: V2,
Metadata: Metadata{
Name: `{{index . "email"}}`,
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
MaxSessionTTL: NewDuration(30 * 60 * time.Minute),
Logins: []string{`{{index . "nickname"}}`, `root`},
NodeLabels: map[string]string{"*": "*"},
},
},
},
},
},
}
// create some claims
var claims = make(jose.Claims)
claims.Add("roles", "teleport-user")
claims.Add("email", "foo@example.com")
claims.Add("nickname", "foo")
claims.Add("full_name", "foo bar")
role, err := oidcConnector.RoleFromTemplate(claims)
c.Assert(err, check.IsNil)
outRole, err := NewRole("foo@example.com", RoleSpecV2{
Logins: []string{"foo", "root"},
MaxSessionTTL: NewDuration(30 * time.Hour),
NodeLabels: map[string]string{"*": "*"},
Namespaces: []string{"default"},
Resources: nil,
ForwardAgent: false,
})
c.Assert(err, check.IsNil)
c.Assert(role, check.DeepEquals, outRole)
}

View file

@ -118,11 +118,14 @@ const (
// KindTrustedCluster is a resource that contains trusted cluster configuration.
KindTrustedCluster = "trusted_cluster"
// V2 is our current version
// V3 is the third version of resources.
V3 = "v3"
// V2 is the second version of resources.
V2 = "v2"
// V1 is our first version
// resources were not explicitly versioned at that point
// V1 is the first version of resources. Note: The first version was
// not explicitly versioned.
V1 = "v1"
)
@ -179,7 +182,7 @@ const V2SchemaTemplate = `{
"version": {"type": "string", "default": "v2"},
"metadata": %v,
"spec": %v
}
}%v
}`
// MetadataSchema is a schema for resource metadata
@ -202,6 +205,9 @@ const MetadataSchema = `{
}
}`
// DefaultDefinitions the default list of JSON schema definitions which is none.
const DefaultDefinitions = ``
// UnknownResource is used to detect resources
type UnknownResource struct {
ResourceHeader

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,7 @@ package services
import (
"encoding/json"
"fmt"
"testing"
"time"
@ -34,6 +35,7 @@ type RoleSuite struct {
}
var _ = Suite(&RoleSuite{})
var _ = fmt.Printf
func (s *RoleSuite) SetUpSuite(c *C) {
utils.InitLoggerForTests()
@ -49,104 +51,103 @@ func (s *RoleSuite) TestRoleExtension(c *C) {
}
in := `{"kind": "role", "metadata": {"name": "name1"}, "spec": {"a": "b"}}`
var role ExtendedRole
err := utils.UnmarshalWithSchema(GetRoleSchema(`"a": {"type": "string"}`), &role, []byte(in))
err := utils.UnmarshalWithSchema(GetRoleSchema(V2, `"a": {"type": "string"}`), &role, []byte(in))
c.Assert(err, IsNil)
c.Assert(role.Spec.A, Equals, "b")
// this is a bad type
in = `{"kind": "role", "metadata": {"name": "name1"}, "spec": {"a": 12}}`
err = utils.UnmarshalWithSchema(GetRoleSchema(`"a": {"type": "string"}`), &role, []byte(in))
err = utils.UnmarshalWithSchema(GetRoleSchema(V2, `"a": {"type": "string"}`), &role, []byte(in))
c.Assert(err, NotNil)
}
func (s *RoleSuite) TestRoleParse(c *C) {
testCases := []struct {
in string
role RoleV2
role RoleV3
error error
}{
// 0 - no input, should not parse
{
in: ``,
error: trace.BadParameter("empty input"),
},
// 1 - validation error, no name
{
in: `{}`,
error: trace.BadParameter("failed to validate: name: name is required"),
},
// 2 - validation error, no name
{
in: `{"kind": "role"}`,
error: trace.BadParameter("failed to validate: name: name is required"),
},
// 3 - role with no spec
{
in: `{"kind": "role", "metadata": {"name": "name1"}, "spec": {}}`,
role: RoleV2{
in: `{"kind": "role", "version": "v3", "metadata": {"name": "name1"}, "spec": {}}`,
role: RoleV3{
Kind: KindRole,
Version: V2,
Version: V3,
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{},
Spec: RoleSpecV3{},
},
},
// 4 - full valid role
{
in: `{
"kind": "role",
"metadata": {"name": "name1"},
"spec": {
"max_session_ttl": "20h",
"node_labels": {"a": "b"},
"namespaces": ["system", "default"],
"resources": {
"role": ["read", "write"]
"kind": "role",
"version": "v3",
"metadata": {"name": "name1"},
"spec": {
"options": {
"max_session_ttl": "20h"
},
"allow": {
"node_labels": {"a": "b"},
"namespaces": ["system", "default"],
"rules": [
{
"resources": ["role"],
"verbs": ["read", "write"]
}
]
},
"deny": {
"logins": ["c"]
}
}
}`,
role: RoleV2{
}
}`,
role: RoleV3{
Kind: KindRole,
Version: V2,
Version: V3,
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
MaxSessionTTL: Duration{20 * time.Hour},
NodeLabels: map[string]string{"a": "b"},
Namespaces: []string{"system", "default"},
Resources: map[string][]string{"role": {ActionRead, ActionWrite}},
},
},
},
{
in: `kind: role
metadata:
name: name1
spec:
max_session_ttl: 20h
node_labels:
a: b
namespaces: ["system", "default"]
resources:
role: [read, write]
`,
role: RoleV2{
Kind: KindRole,
Version: V2,
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
MaxSessionTTL: Duration{20 * time.Hour},
NodeLabels: map[string]string{"a": "b"},
Namespaces: []string{"system", "default"},
Resources: map[string][]string{"role": {ActionRead, ActionWrite}},
Spec: RoleSpecV3{
Options: RoleOptions{
MaxSessionTTL: NewDuration(20 * time.Hour),
},
Allow: RoleConditions{
NodeLabels: map[string]string{"a": "b"},
Namespaces: []string{"system", "default"},
Rules: map[string][]string{
"role": []string{ActionRead, ActionWrite},
},
},
Deny: RoleConditions{
Logins: []string{"c"},
},
},
},
},
}
for i, tc := range testCases {
comment := Commentf("test case %v", i)
role, err := UnmarshalRole([]byte(tc.in))
if tc.error != nil {
c.Assert(err, NotNil, comment)
@ -154,7 +155,7 @@ spec:
c.Assert(err, IsNil, comment)
c.Assert(*role, DeepEquals, tc.role, comment)
out, err := json.Marshal(*role)
out, err := json.Marshal(role)
c.Assert(err, IsNil, comment)
role2, err := UnmarshalRole(out)
@ -296,7 +297,7 @@ func (s *RoleSuite) TestCheckAccess(c *C) {
var set RoleSet
for i := range tc.roles {
set = append(set, &tc.roles[i])
set = append(set, tc.roles[i].V3())
}
for j, check := range tc.checks {
comment := Commentf("test case %v '%v', check %v", i, tc.name, j)
@ -320,27 +321,31 @@ func (s *RoleSuite) TestCheckResourceAccess(c *C) {
}
testCases := []struct {
name string
roles []RoleV2
roles []RoleV3
checks []check
}{
{
name: "empty role set has access to nothing",
roles: []RoleV2{},
name: "0 - empty role set has access to nothing",
roles: []RoleV3{},
checks: []check{
{resource: KindUser, action: ActionWrite, namespace: defaults.Namespace, hasAccess: false},
},
},
{
name: "user can read sessions in default namespace",
roles: []RoleV2{
RoleV2{
name: "1 - user can read sessions in default namespace",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
Namespaces: []string{defaults.Namespace},
Resources: map[string][]string{KindSession: []string{ActionRead}},
Spec: RoleSpecV3{
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
SystemResources: map[string][]string{
KindSession: []string{ActionRead},
},
},
},
},
},
@ -350,26 +355,34 @@ func (s *RoleSuite) TestCheckResourceAccess(c *C) {
},
},
{
name: "user can read sessions in system namespace and write stuff in default namespace",
roles: []RoleV2{
RoleV2{
name: "1 - user can read sessions in system namespace and write stuff in default namespace",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
Namespaces: []string{"system"},
Resources: map[string][]string{KindSession: []string{ActionRead}},
Spec: RoleSpecV3{
Allow: RoleConditions{
Namespaces: []string{"system"},
SystemResources: map[string][]string{
KindSession: []string{ActionRead},
},
},
},
},
RoleV2{
RoleV3{
Metadata: Metadata{
Name: "name2",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV2{
Namespaces: []string{defaults.Namespace},
Resources: map[string][]string{KindSession: []string{ActionWrite, ActionRead}},
Spec: RoleSpecV3{
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
SystemResources: map[string][]string{
KindSession: []string{ActionWrite, ActionRead},
},
},
},
},
},
@ -380,16 +393,68 @@ func (s *RoleSuite) TestCheckResourceAccess(c *C) {
{resource: KindRole, action: ActionRead, namespace: defaults.Namespace, hasAccess: false},
},
},
{
name: "3 - deny rules override allow rules",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV3{
Deny: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: map[string][]string{
KindSession: []string{ActionWrite},
},
},
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: map[string][]string{
KindSession: []string{ActionWrite},
},
},
},
},
},
checks: []check{
{resource: KindSession, action: ActionWrite, namespace: defaults.Namespace, hasAccess: false},
},
},
{
name: "4 - rules override system resources",
roles: []RoleV3{
RoleV3{
Metadata: Metadata{
Name: "name1",
Namespace: defaults.Namespace,
},
Spec: RoleSpecV3{
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
Rules: map[string][]string{
KindSession: []string{ActionWrite},
},
SystemResources: map[string][]string{
KindSession: []string{ActionRead},
},
},
},
},
},
checks: []check{
{resource: KindSession, action: ActionWrite, namespace: defaults.Namespace, hasAccess: true},
},
},
}
for i, tc := range testCases {
var set RoleSet
for i := range tc.roles {
set = append(set, &tc.roles[i])
}
for j, check := range tc.checks {
comment := Commentf("test case %v '%v', check %v", i, tc.name, j)
result := set.CheckResourceAction(check.namespace, check.resource, check.action)
result := set.CheckAccessToRuleOrResource(check.namespace, check.resource, check.action)
if check.hasAccess {
c.Assert(result, IsNil, comment)
} else {

View file

@ -320,7 +320,7 @@ func (o *SAMLConnectorV2) Equals(other SAMLConnector) bool {
if (a.RoleTemplate != nil && b.RoleTemplate == nil) || (a.RoleTemplate == nil && b.RoleTemplate != nil) {
return false
}
if a.RoleTemplate != nil && !a.RoleTemplate.Equals(b.RoleTemplate) {
if a.RoleTemplate != nil && !a.RoleTemplate.Equals(b.RoleTemplate.V3()) {
return false
}
}
@ -516,7 +516,7 @@ func (o *SAMLConnectorV2) RoleFromTemplate(assertionInfo saml2.AssertionInfo) (R
return nil, trace.Wrap(err)
}
return roleTemplate, nil
return roleTemplate.V3(), nil
}
}
}
@ -788,7 +788,7 @@ var AttributeMappingSchema = fmt.Sprintf(`{
},
"role_template": %v
}
}`, GetRoleSchema(""))
}`, GetRoleSchema(V2, ""))
// SigningKeyPairSchema
var SigningKeyPairSchema = `{

View file

@ -411,7 +411,7 @@ func (c *CommandLabels) SetEnv(v string) error {
// GetServerSchema returns role schema with optionally injected
// schema for extensions
func GetServerSchema() string {
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, ServerSpecV2Schema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, ServerSpecV2Schema, DefaultDefinitions)
}
// UnmarshalServerResource unmarshals role from JSON or YAML,

View file

@ -348,7 +348,7 @@ func GetWebSessionSchema() string {
// GetWebSessionSchemaWithExtensions returns JSON Schema for web session with user-supplied extensions
func GetWebSessionSchemaWithExtensions(extension string) string {
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(WebSessionSpecV2Schema, extension))
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(WebSessionSpecV2Schema, extension), DefaultDefinitions)
}
type TeleportWebSessionMarshaler struct{}

View file

@ -378,19 +378,25 @@ func (s *ServicesTestSuite) RolesCRUD(c *C) {
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
role := services.RoleV2{
role := services.RoleV3{
Kind: services.KindRole,
Version: services.V2,
Version: services.V3,
Metadata: services.Metadata{
Name: "role1",
Namespace: defaults.Namespace,
},
Spec: services.RoleSpecV2{
Logins: []string{"root", "bob"},
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
MaxSessionTTL: services.Duration{Duration: time.Hour},
Namespaces: []string{"default", "system"},
Resources: map[string][]string{services.KindRole: []string{services.ActionRead}},
Spec: services.RoleSpecV3{
Options: services.RoleOptions{
services.MaxSessionTTL: services.Duration{Duration: time.Hour},
},
Allow: services.RoleConditions{
Logins: []string{"root", "bob"},
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
Namespaces: []string{"default", "system"},
Rules: map[string][]string{
services.KindRole: services.RO(),
},
},
},
}
err = s.Access.UpsertRole(&role, backend.Forever)
@ -399,7 +405,7 @@ func (s *ServicesTestSuite) RolesCRUD(c *C) {
c.Assert(err, IsNil)
c.Assert(rout, DeepEquals, &role)
role.Spec.Logins = []string{"bob"}
role.Spec.Allow.Logins = []string{"bob"}
err = s.Access.UpsertRole(&role, backend.Forever)
c.Assert(err, IsNil)
rout, err = s.Access.GetRole(role.Metadata.Name)

View file

@ -189,7 +189,7 @@ func (r *ReverseTunnelV1) V2() *ReverseTunnelV2 {
// GetReverseTunnelSchema returns role schema with optionally injected
// schema for extensions
func GetReverseTunnelSchema() string {
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, ReverseTunnelSpecV2Schema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, ReverseTunnelSpecV2Schema, DefaultDefinitions)
}
// UnmarshalReverseTunnel unmarshals reverse tunnel from JSON or YAML,

View file

@ -139,7 +139,7 @@ func GetUniversalSecondFactorSchema(extensionSchema string) string {
} else {
authPreferenceSchema = fmt.Sprintf(UniversalSecondFactorSpecSchemaTemplate, ","+extensionSchema)
}
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema, DefaultDefinitions)
}
// UniversalSecondFactorMarshaler implements marshal/unmarshal of UniversalSecondFactor implementations

View file

@ -491,7 +491,7 @@ func GetUserSchema(extensionSchema string) string {
} else {
userSchema = fmt.Sprintf(UserSpecV2SchemaTemplate, ExternalIdentitySchema, ExternalIdentitySchema, LoginStatusSchema, CreatedBySchema, ", "+extensionSchema)
}
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, userSchema)
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, userSchema, DefaultDefinitions)
}
type TeleportUserMarshaler struct{}

View file

@ -225,7 +225,9 @@ func (s *SrvSuite) TestAgentForward(c *C) {
roleName := services.RoleNameForUser(s.user)
role, err := s.a.GetRole(roleName)
c.Assert(err, IsNil)
role.SetForwardAgent(true)
roleOptions := role.GetOptions()
roleOptions.Set(services.ForwardAgent, true)
role.SetOptions(roleOptions)
err = s.a.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
@ -917,8 +919,10 @@ func newUpack(username string, allowedLogins []string, a *auth.AuthServer) (*upa
return nil, trace.Wrap(err)
}
role := services.RoleForUser(user)
role.SetResource(services.Wildcard, services.RW())
role.SetLogins(allowedLogins)
resources := role.GetSystemResources(services.Allow)
resources[services.Wildcard] = services.RW()
role.SetSystemResources(services.Allow, resources)
role.SetLogins(services.Allow, allowedLogins)
err = a.UpsertRole(role, backend.Forever)
if err != nil {
return nil, trace.Wrap(err)

76
lib/utils/copy.go Normal file
View file

@ -0,0 +1,76 @@
/*
Copyright 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 utils
// CopyStrings makes a deep copy of the passed in string slice and returns
// the copy.
func CopyStrings(in []string) []string {
if in == nil {
return nil
}
out := make([]string, len(in))
copy(out, in)
return out
}
// CopyStringMapSlices makes a deep copy of the passed in map[string][]string
// and returns the copy.
func CopyStringMapSlices(a map[string][]string) map[string][]string {
if a == nil {
return nil
}
out := make(map[string][]string)
for key, values := range a {
vout := make([]string, len(values))
copy(vout, values)
out[key] = vout
}
return out
}
// CopyStringMap makes a deep copy of a map[string]string and returns the copy.
func CopyStringMap(a map[string]string) map[string]string {
if a == nil {
return nil
}
out := make(map[string]string)
for key, value := range a {
out[key] = value
}
return out
}
// CopyStringMapInterface makes a deep copy of the passed in map[string]interface{}
// and returns the copy.
func CopyStringMapInterface(a map[string]interface{}) map[string]interface{} {
if a == nil {
return nil
}
out := make(map[string]interface{})
for key, value := range a {
out[key] = value
}
return out
}

View file

@ -42,6 +42,19 @@ func StringMapsEqual(a, b map[string]string) bool {
return true
}
// InterfaceMapsEqual returns true if two interface maps are equal.
func InterfaceMapsEqual(a, b map[string]interface{}) bool {
if len(a) != len(b) {
return false
}
for key := range a {
if a[key] != b[key] {
return false
}
}
return true
}
// StringMapSlicesEqual returns true if two maps of string slices are equal
func StringMapSlicesEqual(a, b map[string][]string) bool {
if len(a) != len(b) {

View file

@ -33,15 +33,6 @@ import (
"golang.org/x/crypto/ssh"
)
func CopyStrings(in []string) []string {
if in == nil {
return nil
}
out := make([]string, len(in))
copy(out, in)
return out
}
type HostKeyCallback func(hostID string, remote net.Addr, key ssh.PublicKey) error
func ReadPath(path string) ([]byte, error) {

View file

@ -340,7 +340,10 @@ func (m *Handler) getUserACL(w http.ResponseWriter, r *http.Request, _ httproute
accessSet := []*ui.RoleAccess{}
for _, item := range allTeleRoles {
if roleNamesMap[item.GetName()] {
uiRole := ui.NewRole(item)
uiRole, err := ui.NewRole(item)
if err != nil {
return nil, trace.Wrap(err)
}
accessSet = append(accessSet, &uiRole.Access)
}
}
@ -701,7 +704,7 @@ func NewSessionResponse(ctx *SessionContext) (*CreateSessionResponse, error) {
}
roles = append(roles, role)
}
allowedLogins, err := roles.CheckLogins(0)
allowedLogins, err := roles.CheckLoginDuration(0)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -194,8 +194,10 @@ func (s *WebSuite) SetUpTest(c *C) {
teleUser, err := services.NewUser(s.user)
c.Assert(err, IsNil)
role := services.RoleForUser(teleUser)
role.SetLogins([]string{s.user})
role.SetResource(services.Wildcard, services.RW())
role.SetLogins(services.Allow, []string{s.user})
resources := role.GetSystemResources(services.Allow)
resources[services.Wildcard] = services.RW()
role.SetSystemResources(services.Allow, resources)
err = s.authServer.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
@ -435,16 +437,20 @@ func (s *WebSuite) TestSAMLSuccess(c *C) {
c.Assert(err, IsNil)
err = connector.CheckAndSetDefaults()
role, err := services.NewRole(connector.GetAttributesToRoles()[0].Roles[0], services.RoleSpecV2{
MaxSessionTTL: services.NewDuration(defaults.MaxCertDuration),
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
Namespaces: []string{defaults.Namespace},
Resources: map[string][]string{
services.Wildcard: services.RW(),
role, err := services.NewRole(connector.GetAttributesToRoles()[0].Roles[0], services.RoleSpecV3{
Options: services.RoleOptions{
services.MaxSessionTTL: services.NewDuration(defaults.MaxCertDuration),
},
Allow: services.RoleConditions{
NodeLabels: map[string]string{services.Wildcard: services.Wildcard},
Namespaces: []string{defaults.Namespace},
Rules: map[string][]string{
services.Wildcard: services.RW(),
},
},
})
c.Assert(err, IsNil)
role.SetLogins([]string{s.user})
role.SetLogins(services.Allow, []string{s.user})
err = s.roleAuth.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
@ -551,7 +557,7 @@ func (s *WebSuite) authPack(c *C) *authPack {
teleUser, err := services.NewUser(user)
c.Assert(err, IsNil)
role := services.RoleForUser(teleUser)
role.SetLogins([]string{s.user})
role.SetLogins(services.Allow, []string{s.user})
err = s.roleAuth.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
teleUser.AddRole(role.GetName())

View file

@ -1,6 +1,10 @@
package ui
import teleservices "github.com/gravitational/teleport/lib/services"
import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/trace"
)
// Role describes user role consumed by web ui
type Role struct {
@ -13,18 +17,22 @@ type Role struct {
}
// NewRole creates a new instance of UI Role
func NewRole(sRole teleservices.Role) *Role {
func NewRole(sRole services.Role) (*Role, error) {
uiRole := Role{
Name: sRole.GetName(),
}
uiRole.Access.init(sRole)
return &uiRole
err := uiRole.Access.init(sRole)
if err != nil {
return nil, trace.Wrap(err)
}
return &uiRole, nil
}
// ToTeleRole converts UI Role to Storage Role
func (r *Role) ToTeleRole() (teleservices.Role, error) {
teleRole, err := teleservices.NewRole(r.Name, teleservices.RoleSpecV2{})
func (r *Role) ToTeleRole() (services.Role, error) {
teleRole, err := services.NewRole(r.Name, services.RoleSpecV3{})
if err != nil {
return nil, err
}

View file

@ -3,8 +3,10 @@ package ui
import (
"time"
teleservices "github.com/gravitational/teleport/lib/services"
teleutils "github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
)
const (
@ -15,13 +17,13 @@ const (
)
var adminResources = []string{
teleservices.KindRole,
teleservices.KindUser,
teleservices.KindOIDC,
teleservices.KindCertAuthority,
teleservices.KindReverseTunnel,
teleservices.KindTrustedCluster,
teleservices.KindNode,
services.KindRole,
services.KindUser,
services.KindOIDC,
services.KindCertAuthority,
services.KindReverseTunnel,
services.KindTrustedCluster,
services.KindNode,
}
// AdminAccess describes admin access
@ -52,7 +54,7 @@ type RoleAccess struct {
func MergeAccessSet(accessList []*RoleAccess) *RoleAccess {
uiAccess := RoleAccess{}
for _, item := range accessList {
uiAccess.SSH.Logins = teleutils.Deduplicate(append(uiAccess.SSH.Logins, item.SSH.Logins...))
uiAccess.SSH.Logins = utils.Deduplicate(append(uiAccess.SSH.Logins, item.SSH.Logins...))
uiAccess.Admin.Enabled = item.Admin.Enabled || uiAccess.Admin.Enabled
}
@ -60,84 +62,95 @@ func MergeAccessSet(accessList []*RoleAccess) *RoleAccess {
}
// Apply applies this role access to Teleport Role
func (a *RoleAccess) Apply(teleRole teleservices.Role) {
func (a *RoleAccess) Apply(teleRole services.Role) {
a.applyAdmin(teleRole)
a.applySSH(teleRole)
}
func (a *RoleAccess) init(teleRole teleservices.Role) {
func (a *RoleAccess) init(teleRole services.Role) error {
a.initAdmin(teleRole)
a.initSSH(teleRole)
err := a.initSSH(teleRole)
if err != nil {
return trace.Wrap(err)
}
return nil
}
func (a *RoleAccess) initSSH(teleRole teleservices.Role) {
a.SSH.MaxSessionTTL = teleRole.GetMaxSessionTTL().Duration
a.SSH.NodeLabels = teleRole.GetNodeLabels()
func (a *RoleAccess) initSSH(teleRole services.Role) error {
maxSessionTTL, err := teleRole.GetOptions().GetDuration(services.MaxSessionTTL)
if err != nil {
return trace.Wrap(err)
}
a.SSH.MaxSessionTTL = maxSessionTTL.Duration
a.SSH.NodeLabels = teleRole.GetNodeLabels(services.Allow)
// FIXME: this is a workaround for #1623
filteredLogins := []string{}
for _, login := range teleRole.GetLogins() {
for _, login := range teleRole.GetLogins(services.Allow) {
if login != roleDefaultAllowedLogin {
filteredLogins = append(filteredLogins, login)
}
}
a.SSH.Logins = filteredLogins
return nil
}
func (a *RoleAccess) initAdmin(teleRole teleservices.Role) {
hasAllNamespaces := teleservices.MatchNamespace(
teleRole.GetNamespaces(),
teleservices.Wildcard)
func (a *RoleAccess) initAdmin(teleRole services.Role) {
hasAllNamespaces := services.MatchNamespace(
teleRole.GetNamespaces(services.Allow),
services.Wildcard)
resources := teleRole.GetResources()
resources := teleRole.GetSystemResources(services.Allow)
a.Admin.Enabled = hasFullAccess(resources, adminResources) && hasAllNamespaces
}
func (a *RoleAccess) applyAdmin(teleRole teleservices.Role) {
func (a *RoleAccess) applyAdmin(role services.Role) {
if a.Admin.Enabled {
allowAllNamespaces(teleRole)
applyResourceAccess(teleRole, adminResources, teleservices.RW())
allowAllNamespaces(role)
applyResourceAccess(role, adminResources, services.RW())
} else {
teleRole.RemoveResource(teleservices.Wildcard)
applyResourceAccess(teleRole, adminResources, teleservices.RO())
resources := role.GetSystemResources(services.Allow)
delete(resources, services.Wildcard)
role.SetSystemResources(services.Allow, resources)
applyResourceAccess(role, adminResources, services.RO())
}
}
func (a *RoleAccess) applySSH(teleRole teleservices.Role) {
func (a *RoleAccess) applySSH(teleRole services.Role) {
// FIXME: this is a workaround for #1623
if len(a.SSH.Logins) == 0 {
a.SSH.Logins = append(a.SSH.Logins, roleDefaultAllowedLogin)
}
teleRole.SetMaxSessionTTL(a.SSH.MaxSessionTTL)
teleRole.SetLogins(a.SSH.Logins)
teleRole.SetNodeLabels(a.SSH.NodeLabels)
roleOptions := teleRole.GetOptions()
roleOptions[services.MaxSessionTTL] = services.NewDuration(a.SSH.MaxSessionTTL)
teleRole.SetOptions(roleOptions)
teleRole.SetLogins(services.Allow, a.SSH.Logins)
teleRole.SetNodeLabels(services.Allow, a.SSH.NodeLabels)
}
func all() []string {
return []string{teleservices.Wildcard}
return []string{services.Wildcard}
}
func allowAllNamespaces(teleRole teleservices.Role) {
newNamespaces := teleutils.Deduplicate(append(teleRole.GetNamespaces(), all()...))
teleRole.SetNamespaces(newNamespaces)
func allowAllNamespaces(teleRole services.Role) {
newNamespaces := utils.Deduplicate(append(teleRole.GetNamespaces(services.Allow), all()...))
teleRole.SetNamespaces(services.Allow, newNamespaces)
}
func none() []string {
return nil
}
func hasFullAccess(resources map[string][]string, kinds []string) bool {
for _, kind := range kinds {
hasRead := teleservices.MatchResourceAction(
resources,
kind,
teleservices.ActionRead)
hasWrite := teleservices.MatchResourceAction(
resources,
kind,
teleservices.ActionWrite)
func hasFullAccess(rules map[string][]string, resources []string) bool {
for _, resource := range resources {
hasRead := services.MatchRule(rules, resource, services.ActionRead)
hasWrite := services.MatchRule(rules, resource, services.ActionWrite)
if !(hasRead && hasWrite) {
return false
@ -147,8 +160,10 @@ func hasFullAccess(resources map[string][]string, kinds []string) bool {
return true
}
func applyResourceAccess(teleRole teleservices.Role, kinds []string, actions []string) {
for _, kind := range kinds {
teleRole.SetResource(kind, actions)
func applyResourceAccess(role services.Role, resources []string, verbs []string) {
rs := role.GetSystemResources(services.Allow)
for _, resource := range resources {
rs[resource] = verbs
}
role.SetSystemResources(services.Allow, rs)
}

View file

@ -50,10 +50,10 @@ func (r *roleCollection) writeText(w io.Writer) error {
for _, r := range r.roles {
fmt.Fprintf(t, "%v\t%v\t%v\t%v\t%v\n",
r.GetMetadata().Name,
strings.Join(r.GetLogins(), ","),
strings.Join(r.GetNamespaces(), ","),
printNodeLabels(r.GetNodeLabels()),
printActions(r.GetResources()))
strings.Join(r.GetLogins(services.Allow), ","),
strings.Join(r.GetNamespaces(services.Allow), ","),
printNodeLabels(r.GetNodeLabels(services.Allow)),
printActions(r.GetRules(services.Allow)))
}
_, err := io.WriteString(w, t.String())
return trace.Wrap(err)

View file

@ -593,7 +593,7 @@ func hostCAFormat(ca services.CertAuthority, keyBytes []byte, client *auth.TunCl
if err != nil {
return "", trace.Wrap(err)
}
allowedLogins, _ := roles.CheckLogins(defaults.MinCertDuration + time.Second)
allowedLogins, _ := roles.CheckLoginDuration(defaults.MinCertDuration + time.Second)
if len(allowedLogins) > 0 {
comment["logins"] = allowedLogins
}