From 56da8c968c4ca21df9c0dfcbb9a270ed279563c6 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Tue, 22 Oct 2024 20:07:30 -0500 Subject: [PATCH 01/14] fixed bot not being set to system account and email being set to system account --- pkg/connector/users.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pkg/connector/users.go b/pkg/connector/users.go index c0960c01..59d50c0b 100644 --- a/pkg/connector/users.go +++ b/pkg/connector/users.go @@ -25,18 +25,26 @@ func userResource(pId *v2.ResourceId, user types.User) (*v2.Resource, error) { accountType = v2.UserTrait_ACCOUNT_TYPE_HUMAN status v2.UserTrait_Status_Status ) + + // DONE: IsBot is false for @teleport-access-approval-bot + if user.IsBot() { + accountType = v2.UserTrait_ACCOUNT_TYPE_SERVICE + } + + if types.IsSystemResource(user) { + accountType = v2.UserTrait_ACCOUNT_TYPE_SYSTEM + } + firstName, lastName := resource.SplitFullName(user.GetName()) profile := map[string]interface{}{ "name": user.GetName(), - "email": user.GetName(), "user_id": user.GetMetadata().Revision, "first_name": firstName, "last_name": lastName, } - // TODO: IsBot is false for @teleport-access-approval-bot - if user.IsBot() { - accountType = v2.UserTrait_ACCOUNT_TYPE_SERVICE + if accountType == v2.UserTrait_ACCOUNT_TYPE_HUMAN { + profile["email"] = user.GetName() } switch user.GetStatus().IsLocked { @@ -48,18 +56,22 @@ func userResource(pId *v2.ResourceId, user types.User) (*v2.Resource, error) { status = v2.UserTrait_Status_STATUS_UNSPECIFIED } + opts := []resource.UserTraitOption{ + resource.WithUserProfile(profile), + resource.WithUserLogin(user.GetName()), + resource.WithStatus(status), + resource.WithAccountType(accountType), + } + + if accountType == v2.UserTrait_ACCOUNT_TYPE_HUMAN { + opts = append(opts, resource.WithEmail(user.GetName(), true)) + } return resource.NewUserResource( user.GetName(), userResourceType, user.GetName(), - []resource.UserTraitOption{ - resource.WithUserProfile(profile), - // TODO: This is not always an email address, at least not for @teleport-access-approval-bot or bots - resource.WithEmail(user.GetName(), true), - resource.WithUserLogin(user.GetName()), - resource.WithStatus(status), - resource.WithAccountType(accountType), - }, + // DONE: This is not always an email address, at least not for @teleport-access-approval-bot or bots + opts, resource.WithParentResourceID(pId), ) } From 0990ef4df288a8455b98ef7089b68011c6283b29 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Tue, 22 Oct 2024 20:08:37 -0500 Subject: [PATCH 02/14] added default port for proxy address --- pkg/client/client.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 8d082bdd..ec14e925 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -3,6 +3,7 @@ package client import ( "context" "errors" + "strings" "time" teleport "github.com/gravitational/teleport/api/client" @@ -20,6 +21,11 @@ var ErrNoKeyProvided = errors.New("no key provided") const initTimeout = time.Duration(10) * time.Second func New(ctx context.Context, proxyAddress, keyFile, key string) (*TeleportClient, error) { + // DONE: Dial opts are deprecated. We also need to add a default port in proxyAddress if one doesn't exist (to avoid an info message) + if !hasPort(proxyAddress) { + proxyAddress += ":443" + } + tc := &TeleportClient{ ProxyAddress: proxyAddress, } @@ -36,13 +42,9 @@ func New(ctx context.Context, proxyAddress, keyFile, key string) (*TeleportClien return nil, ErrNoKeyProvided } - // TODO: Dial opts are deprecated. We also need to add a default port in proxyAddress if one doesn't exist (to avoid an info message) client, err := teleport.New(ctx, teleport.Config{ Addrs: []string{proxyAddress}, Credentials: []teleport.Credentials{creds}, - // DialOpts: []grpc.DialOption{ - // grpc.WithReturnConnectionError(), - // }, }) if err != nil { return nil, err @@ -66,6 +68,11 @@ func (t *TeleportClient) GetUsers(ctx context.Context) ([]types.User, error) { // GetRoles fetch roles list. func (t *TeleportClient) GetRoles(ctx context.Context) ([]types.Role, error) { return t.client.GetRoles(ctx) +func hasPort(address string) bool { + // remove https and http if it has it + address = strings.TrimPrefix(address, "https://") + address = strings.TrimPrefix(address, "http://") + return len(strings.Split(address, ":")) == 2 } // GetUser gets a user. From 57c100ebf367fbf09a52041ba558aba2bdf811aa Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Tue, 22 Oct 2024 20:09:06 -0500 Subject: [PATCH 03/14] made client public and embedded teleport client --- pkg/client/client.go | 38 ++++---------------------------------- pkg/connector/roles.go | 10 +++++----- pkg/connector/users.go | 2 +- 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index ec14e925..9f15afe9 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -12,7 +12,7 @@ import ( ) type TeleportClient struct { - client *teleport.Client + *teleport.Client ProxyAddress string } @@ -50,24 +50,10 @@ func New(ctx context.Context, proxyAddress, keyFile, key string) (*TeleportClien return nil, err } - tc.SetClient(ctx, client) + tc.Client = client return tc, nil } -func (t *TeleportClient) SetClient(ctx context.Context, c *teleport.Client) { - t.client = c -} - -// TODO: why wrap every client method? We should probably just make the client public - -// GetUsers fetch users list. -func (t *TeleportClient) GetUsers(ctx context.Context) ([]types.User, error) { - return t.client.GetUsers(ctx, false) -} - -// GetRoles fetch roles list. -func (t *TeleportClient) GetRoles(ctx context.Context) ([]types.Role, error) { - return t.client.GetRoles(ctx) func hasPort(address string) bool { // remove https and http if it has it address = strings.TrimPrefix(address, "https://") @@ -75,26 +61,10 @@ func hasPort(address string) bool { return len(strings.Split(address, ":")) == 2 } -// GetUser gets a user. -func (t *TeleportClient) GetUser(ctx context.Context, username string) (types.User, error) { - return t.client.GetUser(ctx, username, false) -} - -// UpdateUserRole updates a user. -func (t *TeleportClient) UpdateUserRole(ctx context.Context, user types.User) (types.User, error) { - return t.client.UpdateUser(ctx, user.(*types.UserV2)) -} +// DONE: why wrap every client method? We should probably just make the client public func (t *TeleportClient) GetNodes(ctx context.Context) (*proto.ListResourcesResponse, error) { - return t.client.GetResources(ctx, &proto.ListResourcesRequest{ + return t.Client.GetResources(ctx, &proto.ListResourcesRequest{ ResourceType: types.KindNode, }) } - -func (t *TeleportClient) GetApps(ctx context.Context) ([]types.Application, error) { - return t.client.GetApps(ctx) -} - -func (t *TeleportClient) GetDatabases(ctx context.Context) ([]types.Database, error) { - return t.client.GetDatabases(ctx) -} diff --git a/pkg/connector/roles.go b/pkg/connector/roles.go index efb54522..53432029 100644 --- a/pkg/connector/roles.go +++ b/pkg/connector/roles.go @@ -84,7 +84,7 @@ func (r *roleBuilder) Grants(ctx context.Context, resource *v2.Resource, token * var rv []*v2.Grant // TODO: look into whether we can use client.ListUsers() as it allows filtering, possibly by role // If we can't, we should try caching the list of all users so we're not re-fetching it on every call to Grants() - users, err := r.client.GetUsers(ctx) + users, err := r.client.GetUsers(ctx, false) if err != nil { return nil, "", nil, err } @@ -136,14 +136,14 @@ func (r *roleBuilder) Grant(ctx context.Context, principal *v2.Resource, entitle return nil, err } - user, err := r.client.GetUser(ctx, userName) + user, err := r.client.GetUser(ctx, userName, false) if err != nil { return nil, err } user.SetLogins(append(user.GetLogins(), userName)) user.AddRole(prodRole.GetName()) - updatedUser, err := r.client.UpdateUserRole(ctx, user) + updatedUser, err := r.client.UpdateUser(ctx, user.(*types.UserV2)) if err != nil { return nil, fmt.Errorf("teleport-connector: failed to add role: %s", err.Error()) } @@ -174,7 +174,7 @@ func (r *roleBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations. roleName := entitlement.Resource.Id.Resource userName := principal.Id.Resource - user, err := r.client.GetUser(ctx, userName) + user, err := r.client.GetUser(ctx, userName, false) if err != nil { return nil, err } @@ -187,7 +187,7 @@ func (r *roleBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations. } user.SetRoles(roleList) - updatedUser, err := r.client.UpdateUserRole(ctx, user) + updatedUser, err := r.client.UpdateUser(ctx, user.(*types.UserV2)) if err != nil { return nil, fmt.Errorf("teleport-connector: failed to revoke role: %s", err.Error()) } diff --git a/pkg/connector/users.go b/pkg/connector/users.go index 59d50c0b..47a4e083 100644 --- a/pkg/connector/users.go +++ b/pkg/connector/users.go @@ -80,7 +80,7 @@ func userResource(pId *v2.ResourceId, user types.User) (*v2.Resource, error) { // Users include a UserTrait because they are the 'shape' of a standard user. func (u *userBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) { var rv []*v2.Resource - users, err := u.client.GetUsers(ctx) + users, err := u.client.GetUsers(ctx, false) if err != nil { return nil, "", nil, err } From 5c8746aa879af10df265657a3da78fe87dd772fe Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Tue, 22 Oct 2024 20:09:56 -0500 Subject: [PATCH 04/14] added auth.pem and *.pem extensions to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b2118062..df39d18c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.so *.dylib *.c1z +auth.pem +*.pem # Test binary, built with `go test -c` *.test From 6d880a2f82be9c542f451d1d6e0c7031085f95ed Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Tue, 22 Oct 2024 20:41:07 -0500 Subject: [PATCH 05/14] added users cache when calling for grants --- pkg/connector/roles.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/pkg/connector/roles.go b/pkg/connector/roles.go index 53432029..4608baf9 100644 --- a/pkg/connector/roles.go +++ b/pkg/connector/roles.go @@ -3,6 +3,7 @@ package connector import ( "context" "fmt" + "sync" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" @@ -22,6 +23,8 @@ const roleMembership = "member" type roleBuilder struct { resourceType *v2.ResourceType client *client.TeleportClient + sync.RWMutex + userCache []types.User } func (r *roleBuilder) ResourceType(_ context.Context) *v2.ResourceType { @@ -47,6 +50,21 @@ func getRoleResource(role types.Role) (*v2.Resource, error) { ) } +func (r *roleBuilder) GetUsers(ctx context.Context) ([]types.User, error) { + if len(r.userCache) != 0 { + return r.userCache, nil + } + + users, err := r.client.GetUsers(ctx, false) + if err != nil { + return []types.User{}, err + } + + r.userCache = users + users = r.userCache + return users, nil +} + // List returns all the roles from the database as resource objects. // Roles include a RoleTrait because they are the 'shape' of a standard role. func (r *roleBuilder) List(ctx context.Context, parentId *v2.ResourceId, token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) { @@ -82,9 +100,15 @@ func (r *roleBuilder) Entitlements(ctx context.Context, resource *v2.Resource, t func (r *roleBuilder) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) { var rv []*v2.Grant - // TODO: look into whether we can use client.ListUsers() as it allows filtering, possibly by role + // DONE: look into whether we can use client.ListUsers() as it allows filtering, possibly by role // If we can't, we should try caching the list of all users so we're not re-fetching it on every call to Grants() - users, err := r.client.GetUsers(ctx, false) + + // NOTE: no way to filter by role + // users, err := r.client.ListUsers(ctx, usersv1.ListUsersRequest{ + // Filter: &usersv1.UserFilter{}, + // }) + + users, err := r.GetUsers(ctx) if err != nil { return nil, "", nil, err } From 532af52c5e43613ca9accd83a6ac9dc1efd29e46 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Thu, 24 Oct 2024 18:29:11 -0500 Subject: [PATCH 06/14] began work on grants, grant and revoke function --- pkg/client/client.go | 4 +- pkg/connector/nodes.go | 93 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 9f15afe9..2397649a 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/conductorone/baton-sdk/pkg/pagination" teleport "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" @@ -63,8 +64,9 @@ func hasPort(address string) bool { // DONE: why wrap every client method? We should probably just make the client public -func (t *TeleportClient) GetNodes(ctx context.Context) (*proto.ListResourcesResponse, error) { +func (t *TeleportClient) GetNodes(ctx context.Context, token *pagination.Token) (*proto.ListResourcesResponse, error) { return t.Client.GetResources(ctx, &proto.ListResourcesRequest{ ResourceType: types.KindNode, + StartKey: token.Token, }) } diff --git a/pkg/connector/nodes.go b/pkg/connector/nodes.go index 39b54819..06a046f4 100644 --- a/pkg/connector/nodes.go +++ b/pkg/connector/nodes.go @@ -7,6 +7,9 @@ import ( v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/gravitational/teleport/api/types" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" ent "github.com/conductorone/baton-sdk/pkg/types/entitlement" rs "github.com/conductorone/baton-sdk/pkg/types/resource" @@ -42,6 +45,7 @@ func getNodeResource(node *Node) (*v2.Resource, error) { rs.WithRoleProfile(map[string]interface{}{ "node_id": node.Id, "node_name": node.Name, + "namespace": node.Namespace, }), }, ) @@ -51,8 +55,8 @@ func getNodeResource(node *Node) (*v2.Resource, error) { // Nodes include a NodeTrait because they are the 'shape' of a standard node. func (n *nodeBuilder) List(ctx context.Context, parentId *v2.ResourceId, token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) { var rv []*v2.Resource - // TODO: client.GetNodes calls GetResources, which is paginated. we need to handle pagination here - nodes, err := n.client.GetNodes(ctx) + // DONE: client.GetNodes calls GetResources, which is paginated. we need to handle pagination here + nodes, err := n.client.GetNodes(ctx, token) if err != nil { return nil, "", nil, err } @@ -75,7 +79,7 @@ func (n *nodeBuilder) List(ctx context.Context, parentId *v2.ResourceId, token * rv = append(rv, rr) } - return rv, "", nil, nil + return rv, nodes.NextKey, nil, nil } func (r *nodeBuilder) Entitlements(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) { @@ -91,15 +95,98 @@ func (r *nodeBuilder) Entitlements(ctx context.Context, resource *v2.Resource, t } // TODO: This should return grants based on who has access to the node resource +// ISSUE: this is more complicated than initially thought. we need to find what roles +// a user needs to access any given node, and then return the grants for those resources +// currently the GetAccessCapabilities should return these values, but is either erroring out +// or returning and empty list, we need to figure out a way to make that function run properly func (r *nodeBuilder) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) { + // nodes, err := r.client.ListResources(ctx, proto.ListResourcesRequest{ + // ResourceType: types.KindNode, + // StartKey: token.Token, + // }) + + // for _, n := range nodes.GetResources() { + // accessCapabilitiesRequest, err := r.client.GetAccessCapabilities(ctx, types.AccessCapabilitiesRequest{ + // RequestableRoles: true, + // ResourceIDs: []types.ResourceID{ + // { + // ClusterName: n.GetNode().GetNamespace(), + // Kind: n.GetNode().GetKind(), + // Name: n.GetNode().GetName(), + // }, + // }, + // }) + // if err != nil { + // return nil, "", nil, err + // } + // + // NOTE: should return the resources applicable roles but is empty or errors out + // fmt.Println(fmt.Sprintf("accessCapabilitiesRequest.ApplicableRolesForResources: %+v", accessCapabilitiesRequest.ApplicableRolesForResources)) + // } + // + // for _, user := range users { + // fmt.Println(fmt.Sprintf("roles: %+v", user.GetRoles())) // return's user's roles + // fmt.Println(fmt.Sprintf("user.GetTraits(): %+v", user.GetTraits())) // returns user's resources + // } + return nil, "", nil, nil } // TODO: these should either grant/revoke access to a node, or we shouldn't implement them func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) { + l := ctxzap.Extract(ctx) + userName := principal.Id.Resource + roleName := entitlement.Resource.Id.Resource + + if principal.Id.ResourceType != userResourceType.Id { + l.Warn( + "baton-segment: only users can be granted role membership", + zap.String("principal_type", principal.Id.ResourceType), + zap.String("principal_id", principal.Id.Resource), + ) + return nil, fmt.Errorf("baton-segment: only users can be granted group membership") + } + + // TODO: check if node can be accessed with given entitlement + + // + + // Create an MFA required role for "prod" nodes. + prodRole, err := types.NewRole(roleName, types.RoleSpecV6{ + Options: types.RoleOptions{ + RequireMFAType: types.RequireMFAType_SESSION, + }, + Allow: types.RoleConditions{ + Logins: []string{userName}, + NodeLabels: types.Labels{}, + }, + }) + if err != nil { + return nil, err + } + + user, err := r.client.GetUser(ctx, userName, false) + if err != nil { + return nil, err + } + + user.SetLogins(append(user.GetLogins(), userName)) + user.AddRole(prodRole.GetName()) + updatedUser, err := r.client.UpdateUser(ctx, user.(*types.UserV2)) + if err != nil { + return nil, fmt.Errorf("teleport-connector: failed to add role: %s", err.Error()) + } + + l.Warn("Role Membership has been created.", + zap.String("Name", updatedUser.GetName()), + zap.String("Namespace", updatedUser.GetMetadata().Namespace), + zap.Time("CreatedAt", updatedUser.GetCreatedBy().Time), + ) + return nil, nil } +// TODO: do the revoke too func (r *nodeBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) { return nil, nil } From 3e9077209dabcb99a14b2414d6096b7ed57605a8 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 13:03:20 -0500 Subject: [PATCH 07/14] added hostname to node names --- pkg/client/client.go | 1 - pkg/connector/nodes.go | 7 +++++-- pkg/connector/roles.go | 7 +++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 2397649a..cc446117 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -63,7 +63,6 @@ func hasPort(address string) bool { } // DONE: why wrap every client method? We should probably just make the client public - func (t *TeleportClient) GetNodes(ctx context.Context, token *pagination.Token) (*proto.ListResourcesResponse, error) { return t.Client.GetResources(ctx, &proto.ListResourcesRequest{ ResourceType: types.KindNode, diff --git a/pkg/connector/nodes.go b/pkg/connector/nodes.go index 06a046f4..509e0430 100644 --- a/pkg/connector/nodes.go +++ b/pkg/connector/nodes.go @@ -65,7 +65,7 @@ func (n *nodeBuilder) List(ctx context.Context, parentId *v2.ResourceId, token * id := node.GetNode().GetRevision() mapNodes[id] = Node{ Id: id, - Name: node.GetNode().GetName(), + Name: node.GetNode().GetHostname(), Namespace: node.GetNode().GetNamespace(), } } @@ -95,6 +95,7 @@ func (r *nodeBuilder) Entitlements(ctx context.Context, resource *v2.Resource, t } // TODO: This should return grants based on who has access to the node resource +// ISSUE: TLDR: we need a way to associate nodes and roles // ISSUE: this is more complicated than initially thought. we need to find what roles // a user needs to access any given node, and then return the grants for those resources // currently the GetAccessCapabilities should return these values, but is either erroring out @@ -133,6 +134,7 @@ func (r *nodeBuilder) Grants(ctx context.Context, resource *v2.Resource, token * } // TODO: these should either grant/revoke access to a node, or we shouldn't implement them +// ISSUE: we need a way to associate nodes and roles func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) { l := ctxzap.Extract(ctx) userName := principal.Id.Resource @@ -186,7 +188,8 @@ func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitle return nil, nil } -// TODO: do the revoke too +// TODO: +// ISSUE: we need a way to associate nodes and roles func (r *nodeBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) { return nil, nil } diff --git a/pkg/connector/roles.go b/pkg/connector/roles.go index 4608baf9..56aea786 100644 --- a/pkg/connector/roles.go +++ b/pkg/connector/roles.go @@ -3,7 +3,6 @@ package connector import ( "context" "fmt" - "sync" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" @@ -23,8 +22,7 @@ const roleMembership = "member" type roleBuilder struct { resourceType *v2.ResourceType client *client.TeleportClient - sync.RWMutex - userCache []types.User + userCache []types.User } func (r *roleBuilder) ResourceType(_ context.Context) *v2.ResourceType { @@ -61,7 +59,6 @@ func (r *roleBuilder) GetUsers(ctx context.Context) ([]types.User, error) { } r.userCache = users - users = r.userCache return users, nil } @@ -83,6 +80,8 @@ func (r *roleBuilder) List(ctx context.Context, parentId *v2.ResourceId, token * rv = append(rv, rr) } + // clear the cache + r.userCache = []types.User{} return rv, "", nil, nil } From 27eb2120131fa26997eb8d67f080543b874b8c13 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 13:16:11 -0500 Subject: [PATCH 08/14] fixed linting issues --- pkg/client/client.go | 1 - pkg/connector/nodes.go | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index cc446117..1da5345e 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -62,7 +62,6 @@ func hasPort(address string) bool { return len(strings.Split(address, ":")) == 2 } -// DONE: why wrap every client method? We should probably just make the client public func (t *TeleportClient) GetNodes(ctx context.Context, token *pagination.Token) (*proto.ListResourcesResponse, error) { return t.Client.GetResources(ctx, &proto.ListResourcesRequest{ ResourceType: types.KindNode, diff --git a/pkg/connector/nodes.go b/pkg/connector/nodes.go index 509e0430..786ed5a3 100644 --- a/pkg/connector/nodes.go +++ b/pkg/connector/nodes.go @@ -99,7 +99,7 @@ func (r *nodeBuilder) Entitlements(ctx context.Context, resource *v2.Resource, t // ISSUE: this is more complicated than initially thought. we need to find what roles // a user needs to access any given node, and then return the grants for those resources // currently the GetAccessCapabilities should return these values, but is either erroring out -// or returning and empty list, we need to figure out a way to make that function run properly +// or returning and empty list, we need to figure out a way to make that function run properly. func (r *nodeBuilder) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) { // nodes, err := r.client.ListResources(ctx, proto.ListResourcesRequest{ // ResourceType: types.KindNode, @@ -134,7 +134,7 @@ func (r *nodeBuilder) Grants(ctx context.Context, resource *v2.Resource, token * } // TODO: these should either grant/revoke access to a node, or we shouldn't implement them -// ISSUE: we need a way to associate nodes and roles +// ISSUE: we need a way to associate nodes and roles. func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) { l := ctxzap.Extract(ctx) userName := principal.Id.Resource @@ -189,7 +189,7 @@ func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitle } // TODO: -// ISSUE: we need a way to associate nodes and roles +// ISSUE: we need a way to associate nodes and roles. func (r *nodeBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) { return nil, nil } From f29d4e794ed1755ee3f9173d61c92811a9dddb8d Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 13:18:01 -0500 Subject: [PATCH 09/14] fixed token name in ci --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3a20332a..c4a4eaf4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,7 +69,7 @@ jobs: uses: teleport-actions/auth@v2 with: proxy: ${{ env.TELEPORT_PROXY }} - token: github-actions-cd + token: baton anonymous-telemetry: 1 - name: Check tsh status run: tsh status @@ -109,4 +109,4 @@ jobs: if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem - baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")" \ No newline at end of file + baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")" From 1af95b6c5b01a76f7e4de309c33cd4c6a4bef935 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 13:25:40 -0500 Subject: [PATCH 10/14] refactored ci yaml --- .github/workflows/ci.yaml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c4a4eaf4..e1b9986f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -52,7 +52,9 @@ jobs: CONNECTOR_ENTITLEMENT: 'role:reviewer:member' CONNECTOR_PRINCIPAL: 'miguel_chavez_m@hotmail.com' CONNECTOR_PRINCIPAL_TYPE: 'user' - TELEPORT_PROXY: ${{ secrets.PROXY }} + BATON_TELEPORT_KEY_PATH: auth.pem + BATON_TELEPORT_PROXY_ADDRESS: ${{ secrets.PROXY }} + steps: - name: Install Go uses: actions/setup-go@v4 @@ -68,7 +70,7 @@ jobs: id: auth uses: teleport-actions/auth@v2 with: - proxy: ${{ env.TELEPORT_PROXY }} + proxy: ${{ secrets.PROXY }} token: baton anonymous-telemetry: 1 - name: Check tsh status @@ -80,7 +82,7 @@ jobs: - name: Build baton-teleport run: go build ./cmd/baton-teleport - name: Run baton-teleport - run: ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem + run: ./baton-teleport - name: Install baton run: ./scripts/get-baton.sh && mv baton /usr/local/bin - name: Get baton resources @@ -88,25 +90,25 @@ jobs: - name: Check for grant before revoking if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem + ./baton-teleport baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")" - name: Revoke grants if: env.CONNECTOR_GRANT != '' run: | - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem --revoke-grant ${{ env.CONNECTOR_GRANT }} + ./baton-teleport + ./baton-teleport --revoke-grant ${{ env.CONNECTOR_GRANT }} - name: Check grant was revoked if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem + ./baton-teleport baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource !=\"${{ env.CONNECTOR_PRINCIPAL }}\")" - name: Grant entitlement if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' && env.CONNECTOR_PRINCIPAL_TYPE != '' run: | - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem --grant-entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --grant-principal ${{ env.CONNECTOR_PRINCIPAL }} --grant-principal-type ${{ env.CONNECTOR_PRINCIPAL_TYPE }} + ./baton-teleport + ./baton-teleport --grant-entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --grant-principal ${{ env.CONNECTOR_PRINCIPAL }} --grant-principal-type ${{ env.CONNECTOR_PRINCIPAL_TYPE }} - name: Check grant was re-granted if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | - ./baton-teleport --teleport-proxy-address ${{ env.TELEPORT_PROXY }} --teleport-key-file auth.pem + ./baton-teleport baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")" From 5cfa8ab18e7b22d4a2ea7738624998b26b664e1b Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 16:32:23 -0500 Subject: [PATCH 11/14] updated ci connector data --- .github/workflows/ci.yaml | 4 +- pkg/connector/nodes.go | 99 +++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e1b9986f..e92c1faa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,9 +48,9 @@ jobs: env: BATON_LOG_LEVEL: debug # The following parameters are passed to grant/revoke commands - CONNECTOR_GRANT: 'role:reviewer:member:user:miguel_chavez_m@hotmail.com' + CONNECTOR_GRANT: 'role:reviewer:member:user:alejandro.bernal@conductorone.com' CONNECTOR_ENTITLEMENT: 'role:reviewer:member' - CONNECTOR_PRINCIPAL: 'miguel_chavez_m@hotmail.com' + CONNECTOR_PRINCIPAL: 'alejandro.bernal@conductorone.com' CONNECTOR_PRINCIPAL_TYPE: 'user' BATON_TELEPORT_KEY_PATH: auth.pem BATON_TELEPORT_PROXY_ADDRESS: ${{ secrets.PROXY }} diff --git a/pkg/connector/nodes.go b/pkg/connector/nodes.go index 786ed5a3..be7887fb 100644 --- a/pkg/connector/nodes.go +++ b/pkg/connector/nodes.go @@ -7,9 +7,6 @@ import ( v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" "github.com/conductorone/baton-sdk/pkg/pagination" - "github.com/gravitational/teleport/api/types" - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "go.uber.org/zap" ent "github.com/conductorone/baton-sdk/pkg/types/entitlement" rs "github.com/conductorone/baton-sdk/pkg/types/resource" @@ -136,55 +133,55 @@ func (r *nodeBuilder) Grants(ctx context.Context, resource *v2.Resource, token * // TODO: these should either grant/revoke access to a node, or we shouldn't implement them // ISSUE: we need a way to associate nodes and roles. func (r *nodeBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) { - l := ctxzap.Extract(ctx) - userName := principal.Id.Resource - roleName := entitlement.Resource.Id.Resource - - if principal.Id.ResourceType != userResourceType.Id { - l.Warn( - "baton-segment: only users can be granted role membership", - zap.String("principal_type", principal.Id.ResourceType), - zap.String("principal_id", principal.Id.Resource), - ) - return nil, fmt.Errorf("baton-segment: only users can be granted group membership") - } - - // TODO: check if node can be accessed with given entitlement - + // l := ctxzap.Extract(ctx) + // userName := principal.Id.Resource + // roleName := entitlement.Resource.Id.Resource + // + // if principal.Id.ResourceType != userResourceType.Id { + // l.Warn( + // "baton-segment: only users can be granted role membership", + // zap.String("principal_type", principal.Id.ResourceType), + // zap.String("principal_id", principal.Id.Resource), + // ) + // return nil, fmt.Errorf("baton-segment: only users can be granted group membership") + // } + // + // // TODO: check if node can be accessed with given entitlement + // + // // + // + // // Create an MFA required role for "prod" nodes. + // prodRole, err := types.NewRole(roleName, types.RoleSpecV6{ + // Options: types.RoleOptions{ + // RequireMFAType: types.RequireMFAType_SESSION, + // }, + // Allow: types.RoleConditions{ + // Logins: []string{userName}, + // NodeLabels: types.Labels{}, + // }, + // }) + // if err != nil { + // return nil, err + // } + // + // user, err := r.client.GetUser(ctx, userName, false) + // if err != nil { + // return nil, err + // } + // + // user.SetLogins(append(user.GetLogins(), userName)) + // user.AddRole(prodRole.GetName()) + // updatedUser, err := r.client.UpdateUser(ctx, user.(*types.UserV2)) + // if err != nil { + // return nil, fmt.Errorf("teleport-connector: failed to add role: %s", err.Error()) + // } + // + // l.Warn("Role Membership has been created.", + // zap.String("Name", updatedUser.GetName()), + // zap.String("Namespace", updatedUser.GetMetadata().Namespace), + // zap.Time("CreatedAt", updatedUser.GetCreatedBy().Time), + // ) // - - // Create an MFA required role for "prod" nodes. - prodRole, err := types.NewRole(roleName, types.RoleSpecV6{ - Options: types.RoleOptions{ - RequireMFAType: types.RequireMFAType_SESSION, - }, - Allow: types.RoleConditions{ - Logins: []string{userName}, - NodeLabels: types.Labels{}, - }, - }) - if err != nil { - return nil, err - } - - user, err := r.client.GetUser(ctx, userName, false) - if err != nil { - return nil, err - } - - user.SetLogins(append(user.GetLogins(), userName)) - user.AddRole(prodRole.GetName()) - updatedUser, err := r.client.UpdateUser(ctx, user.(*types.UserV2)) - if err != nil { - return nil, fmt.Errorf("teleport-connector: failed to add role: %s", err.Error()) - } - - l.Warn("Role Membership has been created.", - zap.String("Name", updatedUser.GetName()), - zap.String("Namespace", updatedUser.GetMetadata().Namespace), - zap.Time("CreatedAt", updatedUser.GetCreatedBy().Time), - ) - return nil, nil } From 6b538c5ac471ec41aae592a281667aef3a978e84 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 16:44:47 -0500 Subject: [PATCH 12/14] grant before revoking in case tests fail --- .github/workflows/ci.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e92c1faa..aeedb8b9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -87,6 +87,11 @@ jobs: run: ./scripts/get-baton.sh && mv baton /usr/local/bin - name: Get baton resources run: baton resources + - name: Grant entitlement + if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' && env.CONNECTOR_PRINCIPAL_TYPE != '' + run: | + ./baton-teleport + ./baton-teleport --grant-entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --grant-principal ${{ env.CONNECTOR_PRINCIPAL }} --grant-principal-type ${{ env.CONNECTOR_PRINCIPAL_TYPE }} - name: Check for grant before revoking if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | From ae9e2adc76973577501739d551e89b87ca8d3e10 Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 16:48:00 -0500 Subject: [PATCH 13/14] if it fails to find the grant then it was revoked correctly --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aeedb8b9..1ff563d6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -106,7 +106,7 @@ jobs: if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' run: | ./baton-teleport - baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource !=\"${{ env.CONNECTOR_PRINCIPAL }}\")" + baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource !=\"${{ env.CONNECTOR_PRINCIPAL }}\")" || exit 0 - name: Grant entitlement if: env.CONNECTOR_ENTITLEMENT != '' && env.CONNECTOR_PRINCIPAL != '' && env.CONNECTOR_PRINCIPAL_TYPE != '' run: | From dc4ddf13a2fb2d20f81942a2ee6052cce145121a Mon Sep 17 00:00:00 2001 From: Alejandro Bernal Date: Fri, 25 Oct 2024 16:51:37 -0500 Subject: [PATCH 14/14] remove DONE comments --- pkg/client/client.go | 1 - pkg/connector/nodes.go | 1 - pkg/connector/roles.go | 8 -------- pkg/connector/users.go | 2 -- 4 files changed, 12 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 1da5345e..487ac95b 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -22,7 +22,6 @@ var ErrNoKeyProvided = errors.New("no key provided") const initTimeout = time.Duration(10) * time.Second func New(ctx context.Context, proxyAddress, keyFile, key string) (*TeleportClient, error) { - // DONE: Dial opts are deprecated. We also need to add a default port in proxyAddress if one doesn't exist (to avoid an info message) if !hasPort(proxyAddress) { proxyAddress += ":443" } diff --git a/pkg/connector/nodes.go b/pkg/connector/nodes.go index be7887fb..528a1c1e 100644 --- a/pkg/connector/nodes.go +++ b/pkg/connector/nodes.go @@ -52,7 +52,6 @@ func getNodeResource(node *Node) (*v2.Resource, error) { // Nodes include a NodeTrait because they are the 'shape' of a standard node. func (n *nodeBuilder) List(ctx context.Context, parentId *v2.ResourceId, token *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) { var rv []*v2.Resource - // DONE: client.GetNodes calls GetResources, which is paginated. we need to handle pagination here nodes, err := n.client.GetNodes(ctx, token) if err != nil { return nil, "", nil, err diff --git a/pkg/connector/roles.go b/pkg/connector/roles.go index 56aea786..6a45e67c 100644 --- a/pkg/connector/roles.go +++ b/pkg/connector/roles.go @@ -99,14 +99,6 @@ func (r *roleBuilder) Entitlements(ctx context.Context, resource *v2.Resource, t func (r *roleBuilder) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) { var rv []*v2.Grant - // DONE: look into whether we can use client.ListUsers() as it allows filtering, possibly by role - // If we can't, we should try caching the list of all users so we're not re-fetching it on every call to Grants() - - // NOTE: no way to filter by role - // users, err := r.client.ListUsers(ctx, usersv1.ListUsersRequest{ - // Filter: &usersv1.UserFilter{}, - // }) - users, err := r.GetUsers(ctx) if err != nil { return nil, "", nil, err diff --git a/pkg/connector/users.go b/pkg/connector/users.go index 47a4e083..bfcb20a7 100644 --- a/pkg/connector/users.go +++ b/pkg/connector/users.go @@ -26,7 +26,6 @@ func userResource(pId *v2.ResourceId, user types.User) (*v2.Resource, error) { status v2.UserTrait_Status_Status ) - // DONE: IsBot is false for @teleport-access-approval-bot if user.IsBot() { accountType = v2.UserTrait_ACCOUNT_TYPE_SERVICE } @@ -70,7 +69,6 @@ func userResource(pId *v2.ResourceId, user types.User) (*v2.Resource, error) { user.GetName(), userResourceType, user.GetName(), - // DONE: This is not always an email address, at least not for @teleport-access-approval-bot or bots opts, resource.WithParentResourceID(pId), )