Add OSS support for kubernetes groups

* Add k8s-groups flag for tctl users add
* Add kubernetes_groups field in github connector
This commit is contained in:
Sasha Klizhentas
2018-08-06 17:21:21 -07:00
committed by Alexander Klizhentas
parent bdb3f0608d
commit 43cb9559a5
14 changed files with 147 additions and 40 deletions
+10 -2
View File
@@ -325,9 +325,17 @@ const (
// allowed logins.
TraitLogins = "logins"
// TraitInternalRoleVariable is the role variable used to store allowed
// TraitKubeGroups is the name the role variable used to store
// allowed kubernetes groups
TraitKubeGroups = "kubernetes_groups"
// TraitInternalLoginsVariable is the variable used to store allowed
// logins for local accounts.
TraitInternalRoleVariable = "{{internal.logins}}"
TraitInternalLoginsVariable = "{{internal.logins}}"
// TraitInternalKubeGroupsVariable is the variable used to store allowed
// kubernetes groups for local accounts.
TraitInternalKubeGroupsVariable = "{{internal.kubernetes_groups}}"
)
// SCP is Secure Copy.
+1 -1
Submodule e updated: a9c7730334...1984ef6ee7
+23
View File
@@ -32,6 +32,7 @@ import (
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/suite"
"github.com/gravitational/teleport/lib/utils"
@@ -612,6 +613,28 @@ func (s *AuthSuite) TestMigrateIdentity(c *C) {
fixtures.DeepCompare(c, newid, oldid)
}
// TestMigrateAdminRole tests migration of the admin role
func (s *AuthSuite) TestMigrateAdminRole(c *C) {
defaultRole := services.NewAdminRole()
defaultRole.SetKubeGroups(services.Allow, nil)
err := s.a.UpsertRole(defaultRole, backend.Forever)
c.Assert(err, IsNil)
err = migrateAdminRole(s.a)
c.Assert(err, IsNil)
out, err := s.a.GetRole(defaultRole.GetName())
c.Assert(err, IsNil)
c.Assert(out.GetKubeGroups(services.Allow), DeepEquals, modules.GetModules().DefaultKubeGroups())
// second call does nothing
err = migrateAdminRole(s.a)
c.Assert(err, IsNil)
out, err = s.a.GetRole(defaultRole.GetName())
c.Assert(err, IsNil)
c.Assert(out.GetKubeGroups(services.Allow), DeepEquals, modules.GetModules().DefaultKubeGroups())
}
// writeKeys saves the key/cert pair for a given domain onto disk. This usually means the
// domain trusts us (signed our public key)
func writeKeys(dataDir string, id IdentityID, key []byte, sshCert []byte, tlsCert []byte, tlsCACert []byte) error {
+2 -2
View File
@@ -212,7 +212,7 @@ func (s *AuthServer) validateGithubAuthCallback(q url.Values) (*GithubAuthRespon
}
func (s *AuthServer) createGithubUser(connector services.GithubConnector, claims services.GithubClaims) error {
logins := connector.MapClaims(claims)
logins, kubeGroups := connector.MapClaims(claims)
if len(logins) == 0 {
return trace.BadParameter(
"user %q does not belong to any teams configured in %q connector",
@@ -230,7 +230,7 @@ func (s *AuthServer) createGithubUser(connector services.GithubConnector, claims
},
Spec: services.UserSpecV2{
Roles: modules.GetModules().RolesFromLogins(logins),
Traits: modules.GetModules().TraitsFromLogins(logins),
Traits: modules.GetModules().TraitsFromLogins(logins, kubeGroups),
Expires: s.clock.Now().UTC().Add(defaults.OAuth2TTL),
GithubIdentities: []services.ExternalIdentity{{
ConnectorID: connector.GetName(),
+18
View File
@@ -397,6 +397,10 @@ func migrateLegacyResources(cfg InitConfig, asrv *AuthServer) error {
if err != nil {
return trace.Wrap(err)
}
err = migrateAdminRole(asrv)
if err != nil {
return trace.Wrap(err)
}
return nil
}
@@ -467,6 +471,20 @@ func migrateUsers(asrv *AuthServer) error {
return nil
}
func migrateAdminRole(asrv *AuthServer) error {
defaultRole := services.NewAdminRole()
role, err := asrv.GetRole(defaultRole.GetName())
if err != nil {
return trace.Wrap(err)
}
if len(role.GetKubeGroups(services.Allow)) != 0 {
return nil
}
log.Infof("Migrating default admin role, adding default kubernetes groups.")
role.SetKubeGroups(services.Allow, defaultRole.GetKubeGroups(services.Allow))
return asrv.UpsertRole(role, backend.Forever)
}
// isFirstStart returns 'true' if the auth server is starting for the 1st time
// on this server.
func isFirstStart(authServer *AuthServer, cfg InitConfig) (bool, error) {
+13 -5
View File
@@ -33,14 +33,16 @@ type Modules interface {
EmptyRolesHandler() error
// DefaultAllowedLogins returns default allowed logins for a new admin role
DefaultAllowedLogins() []string
// DefaultKubeGroups returns default kuberentes groups for a new admin role
DefaultKubeGroups() []string
// PrintVersion prints teleport version
PrintVersion()
// RolesFromLogins returns roles for external user based on the logins
// extracted from the connector
RolesFromLogins([]string) []string
// TraitsFromLogins returns traits for external user based on the logins
// extracted from the connector
TraitsFromLogins([]string) map[string][]string
// and kubernetes groups extracted from the connector
TraitsFromLogins([]string, []string) map[string][]string
}
// SetModules sets the modules interface
@@ -65,9 +67,14 @@ func (p *defaultModules) EmptyRolesHandler() error {
return nil
}
// DefaultKubeGroups returns default kuberentes groups for a new admin role
func (p *defaultModules) DefaultKubeGroups() []string {
return []string{teleport.TraitInternalKubeGroupsVariable}
}
// DefaultAllowedLogins returns allowed logins for a new admin role
func (p *defaultModules) DefaultAllowedLogins() []string {
return []string{teleport.TraitInternalRoleVariable}
return []string{teleport.TraitInternalLoginsVariable}
}
// PrintVersion prints teleport version
@@ -92,9 +99,10 @@ func (p *defaultModules) RolesFromLogins(logins []string) []string {
// extracted from the connector
//
// By default logins are treated as allowed logins user traits.
func (p *defaultModules) TraitsFromLogins(logins []string) map[string][]string {
func (p *defaultModules) TraitsFromLogins(logins []string, kubeGroups []string) map[string][]string {
return map[string][]string{
teleport.TraitLogins: logins,
teleport.TraitLogins: logins,
teleport.TraitKubeGroups: kubeGroups,
}
}
+13 -5
View File
@@ -36,14 +36,18 @@ func (s *ModulesSuite) TestDefaultModules(c *check.C) {
c.Assert(err, check.IsNil)
logins := GetModules().DefaultAllowedLogins()
c.Assert(logins, check.DeepEquals, []string{teleport.TraitInternalRoleVariable})
c.Assert(logins, check.DeepEquals, []string{teleport.TraitInternalLoginsVariable})
kubeGroups := GetModules().DefaultKubeGroups()
c.Assert(kubeGroups, check.DeepEquals, []string{teleport.TraitInternalKubeGroupsVariable})
roles := GetModules().RolesFromLogins([]string{"root"})
c.Assert(roles, check.DeepEquals, []string{teleport.AdminRoleName})
traits := GetModules().TraitsFromLogins([]string{"root"})
traits := GetModules().TraitsFromLogins([]string{"root"}, []string{"system:masters"})
c.Assert(traits, check.DeepEquals, map[string][]string{
teleport.TraitLogins: []string{"root"},
teleport.TraitLogins: []string{"root"},
teleport.TraitKubeGroups: []string{"system:masters"},
})
}
@@ -59,7 +63,7 @@ func (s *ModulesSuite) TestTestModules(c *check.C) {
roles := GetModules().RolesFromLogins([]string{"root"})
c.Assert(roles, check.DeepEquals, []string{"root"})
traits := GetModules().TraitsFromLogins([]string{"root"})
traits := GetModules().TraitsFromLogins([]string{"root"}, []string{"system:masters"})
c.Assert(traits, check.IsNil)
}
@@ -73,12 +77,16 @@ func (p *testModules) DefaultAllowedLogins() []string {
return []string{"a", "b"}
}
func (p *testModules) DefaultKubeGroups() []string {
return []string{"kube:test"}
}
func (p *testModules) PrintVersion() {}
func (p *testModules) RolesFromLogins(logins []string) []string {
return logins
}
func (p *testModules) TraitsFromLogins(logins []string) map[string][]string {
func (p *testModules) TraitsFromLogins(logins []string, kubeGroups []string) map[string][]string {
return nil
}
+17 -6
View File
@@ -51,7 +51,8 @@ type GithubConnector interface {
// SetTeamsToLogins sets the mapping of Github teams to allowed logins
SetTeamsToLogins([]TeamMapping)
// MapClaims returns the list of allows logins based on the retrieved claims
MapClaims(GithubClaims) []string
// returns list of logins and kubernetes groups
MapClaims(GithubClaims) ([]string, []string)
// GetDisplay returns the connector display name
GetDisplay() string
// SetDisplay sets the connector display name
@@ -104,7 +105,9 @@ type TeamMapping struct {
// Team is a team within the organization a user belongs to
Team string `json:"team"`
// Logins is a list of allowed logins for this org/team
Logins []string `json:"logins"`
Logins []string `json:"logins,omitempty"`
// KubeGroups is a list of allowed kubernetes groups for this org/team
KubeGroups []string `json:"kubernetes_groups,omitempty"`
}
// GithubClaims represents Github user information obtained during OAuth2 flow
@@ -203,9 +206,10 @@ func (c *GithubConnectorV3) SetDisplay(display string) {
c.Spec.Display = display
}
// MapClaims returns a list of logins based on the provided claims
func (c *GithubConnectorV3) MapClaims(claims GithubClaims) []string {
var logins []string
// MapClaims returns a list of logins based on the provided claims,
// returns a list of logins and list of kubernetes groups
func (c *GithubConnectorV3) MapClaims(claims GithubClaims) ([]string, []string) {
var logins, kubeGroups []string
for _, mapping := range c.GetTeamsToLogins() {
teams, ok := claims.OrganizationToTeams[mapping.Organization]
if !ok {
@@ -216,10 +220,11 @@ func (c *GithubConnectorV3) MapClaims(claims GithubClaims) []string {
// see if the user belongs to this team
if team == mapping.Team {
logins = append(logins, mapping.Logins...)
kubeGroups = append(kubeGroups, mapping.KubeGroups...)
}
}
}
return utils.Deduplicate(logins)
return utils.Deduplicate(logins), utils.Deduplicate(kubeGroups)
}
var githubConnectorMarshaler GithubConnectorMarshaler = &TeleportGithubConnectorMarshaler{}
@@ -323,6 +328,12 @@ var TeamMappingSchema = `{
"items": {
"type": "string"
}
},
"kubernetes_groups": {
"type": "array",
"items": {
"type": "string"
}
}
}
}`
+19 -4
View File
@@ -76,22 +76,37 @@ func (s *GithubSuite) TestMapClaims(c *check.C) {
Organization: "gravitational",
Team: "admins",
Logins: []string{"admin", "dev"},
KubeGroups: []string{"system:masters", "kube-devs"},
},
{
Organization: "gravitational",
Team: "devs",
Logins: []string{"dev", "test"},
KubeGroups: []string{"kube-devs"},
},
},
})
c.Assert(connector.MapClaims(GithubClaims{
logins, kubeGroups := connector.MapClaims(GithubClaims{
OrganizationToTeams: map[string][]string{
"gravitational": []string{"admins"},
},
}), check.DeepEquals, []string{"admin", "dev"})
c.Assert(connector.MapClaims(GithubClaims{
})
c.Assert(logins, check.DeepEquals, []string{"admin", "dev"})
c.Assert(kubeGroups, check.DeepEquals, []string{"system:masters", "kube-devs"})
logins, kubeGroups = connector.MapClaims(GithubClaims{
OrganizationToTeams: map[string][]string{
"gravitational": []string{"devs"},
},
})
c.Assert(logins, check.DeepEquals, []string{"dev", "test"})
c.Assert(kubeGroups, check.DeepEquals, []string{"kube-devs"})
logins, kubeGroups = connector.MapClaims(GithubClaims{
OrganizationToTeams: map[string][]string{
"gravitational": []string{"admins", "devs"},
},
}), check.DeepEquals, []string{"admin", "dev", "test"})
})
c.Assert(logins, check.DeepEquals, []string{"admin", "dev", "test"})
c.Assert(kubeGroups, check.DeepEquals, []string{"system:masters", "kube-devs"})
}
+11 -10
View File
@@ -22,10 +22,9 @@ import (
"time"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/utils"
"github.com/davecgh/go-spew/spew"
"github.com/kylelemons/godebug/diff"
. "gopkg.in/check.v1"
)
@@ -95,6 +94,7 @@ func (s *MigrationsSuite) TestMigrateUsers(c *C) {
in := &UserV1{
Name: "alice",
AllowedLogins: []string{"admin", "centos"},
KubeGroups: []string{"system:masters"},
OIDCIdentities: []ExternalIdentity{{Username: "alice@example.com", ConnectorID: "example"}},
Status: LoginStatus{
IsLocked: true,
@@ -122,15 +122,16 @@ func (s *MigrationsSuite) TestMigrateUsers(c *C) {
Expires: in.Expires,
CreatedBy: in.CreatedBy,
Traits: map[string][]string{
"logins": in.AllowedLogins,
"logins": in.AllowedLogins,
"kubernetes_groups": in.KubeGroups,
},
},
rawObject: *in,
}
c.Assert(out.rawObject, DeepEquals, *in)
c.Assert(out.Metadata, DeepEquals, expected.Metadata)
c.Assert(out.Spec, DeepEquals, expected.Spec)
c.Assert(out, DeepEquals, expected)
fixtures.DeepCompare(c, out.rawObject, *in)
fixtures.DeepCompare(c, out.Metadata, expected.Metadata)
fixtures.DeepCompare(c, out.Spec, expected.Spec)
fixtures.DeepCompare(c, out, expected)
data, err := json.Marshal(in)
c.Assert(err, IsNil)
@@ -144,11 +145,10 @@ func (s *MigrationsSuite) TestMigrateUsers(c *C) {
out3, err := GetUserMarshaler().UnmarshalUser(data)
c.Assert(err, IsNil)
d := &spew.ConfigState{Indent: " ", DisableMethods: true, DisablePointerMethods: true, DisablePointerAddresses: true}
expected.rawObject = nil
obj := out3.(*UserV2)
obj.rawObject = nil
c.Assert(obj, DeepEquals, expected, Commentf("%v", diff.Diff(d.Sdump(obj), d.Sdump(expected))))
fixtures.DeepCompare(c, obj, expected)
// test backwards compatibility
data, err = GetUserMarshaler().MarshalUser(expected, WithVersion(V1))
@@ -156,7 +156,8 @@ func (s *MigrationsSuite) TestMigrateUsers(c *C) {
var out4 UserV1
json.Unmarshal(data, &out4)
in.AllowedLogins = nil
c.Assert(out4, DeepEquals, *in, Commentf("%v", diff.Diff(d.Sdump(obj), d.Sdump(*in))))
in.KubeGroups = nil
fixtures.DeepCompare(c, out4, *in)
}
func (s *MigrationsSuite) TestMigrateReverseTunnels(c *C) {
+5 -4
View File
@@ -104,6 +104,7 @@ func NewAdminRole() Role {
},
}
role.SetLogins(Allow, modules.GetModules().DefaultAllowedLogins())
role.SetKubeGroups(Allow, modules.GetModules().DefaultKubeGroups())
return role
}
@@ -354,9 +355,9 @@ func applyValueTraits(val string, traits map[string][]string) ([]string, error)
return []string{val}, nil
}
// For internal traits, only internal.logins is supported at the moment.
// For internal traits, only internal.logins and internal.kubernetes_groups is supported at the moment.
if variablePrefix == teleport.TraitInternalPrefix {
if variableName != teleport.TraitLogins {
if variableName != teleport.TraitLogins && variableName != teleport.TraitKubeGroups {
return nil, trace.BadParameter("unsupported variable %q", variableName)
}
}
@@ -701,7 +702,7 @@ type RoleConditions struct {
Rules []Rule `json:"rules,omitempty"`
// KubeGroups is a list of kubernetes groups
KubeGroups []string `json:"kube_groups,omitempty"`
KubeGroups []string `json:"kubernetes_groups,omitempty"`
}
// Equals returns true if the role conditions (logins, namespaces, labels,
@@ -1894,7 +1895,7 @@ const RoleSpecV3SchemaDefinitions = `
"type": "array",
"items": { "type": "string" }
},
"kube_groups": {
"kubernetes_groups": {
"type": "array",
"items": { "type": "string" }
},
+1
View File
@@ -681,6 +681,7 @@ func (s *ServicesTestSuite) GithubConnectorCRUD(c *C) {
Organization: "gravitational",
Team: "admins",
Logins: []string{"admin"},
KubeGroups: []string{"system:masters"},
},
},
},
+6 -1
View File
@@ -475,6 +475,10 @@ type UserV1 struct {
// user is allowed to login as
AllowedLogins []string `json:"allowed_logins"`
// KubeGroups represents a list of kubernetes groups
// this teleport user is allowed to assume
KubeGroups []string `json:"kubernetes_groups,omitempty"`
// OIDCIdentities lists associated OpenID Connect identities
// that let user log in using externally verified identity
OIDCIdentities []ExternalIdentity `json:"oidc_identities"`
@@ -532,7 +536,8 @@ func (u *UserV1) V2() *UserV2 {
CreatedBy: u.CreatedBy,
Roles: u.Roles,
Traits: map[string][]string{
teleport.TraitLogins: u.AllowedLogins,
teleport.TraitLogins: u.AllowedLogins,
teleport.TraitKubeGroups: u.KubeGroups,
},
},
rawObject: *u,
+8
View File
@@ -38,6 +38,7 @@ type UserCommand struct {
config *service.Config
login string
allowedLogins string
kubeGroups string
roles string
identities []string
ttl time.Duration
@@ -57,6 +58,8 @@ func (u *UserCommand) Initialize(app *kingpin.Application, config *service.Confi
u.userAdd.Arg("account", "Teleport user account name").Required().StringVar(&u.login)
u.userAdd.Arg("local-logins", "Local UNIX users this account can log in as [login]").
Default("").StringVar(&u.allowedLogins)
u.userAdd.Flag("k8s-groups", "Kubernetes groups to assign to a user.").
Default("").StringVar(&u.kubeGroups)
u.userAdd.Flag("ttl", fmt.Sprintf("Set expiration time for token, default is %v hour, maximum is %v hours",
int(defaults.SignupTokenTTL/time.Hour), int(defaults.MaxSignupTokenTTL/time.Hour))).
Default(fmt.Sprintf("%v", defaults.SignupTokenTTL)).DurationVar(&u.ttl)
@@ -98,9 +101,14 @@ func (u *UserCommand) Add(client auth.ClientI) error {
if u.allowedLogins == "" {
u.allowedLogins = u.login
}
var kubeGroups []string
if u.kubeGroups != "" {
kubeGroups = strings.Split(u.kubeGroups, ",")
}
user := services.UserV1{
Name: u.login,
AllowedLogins: strings.Split(u.allowedLogins, ","),
KubeGroups: kubeGroups,
}
token, err := client.CreateSignupToken(user, u.ttl)
if err != nil {