Skip to content

Commit

Permalink
Merge pull request #24 from ConductorOne/abernal/todos
Browse files Browse the repository at this point in the history
fix TODOs
  • Loading branch information
aldevv authored Oct 28, 2024
2 parents 2d66660 + dc4ddf1 commit 9041fbc
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 79 deletions.
37 changes: 22 additions & 15 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ 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'
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
Expand All @@ -68,8 +70,8 @@ jobs:
id: auth
uses: teleport-actions/auth@v2
with:
proxy: ${{ env.TELEPORT_PROXY }}
token: github-actions-cd
proxy: ${{ secrets.PROXY }}
token: baton
anonymous-telemetry: 1
- name: Check tsh status
run: tsh status
Expand All @@ -80,33 +82,38 @@ 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
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: |
./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 grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource !=\"${{ env.CONNECTOR_PRINCIPAL }}\")"
./baton-teleport
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: |
./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 grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")"
./baton-teleport
baton grants --entitlement ${{ env.CONNECTOR_ENTITLEMENT }} --output-format=json | jq -e ".grants | any(.principal.id.resource ==\"${{ env.CONNECTOR_PRINCIPAL }}\")"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*.so
*.dylib
*.c1z
auth.pem
*.pem

# Test binary, built with `go test -c`
*.test
Expand Down
56 changes: 16 additions & 40 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ package client
import (
"context"
"errors"
"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"
)

type TeleportClient struct {
client *teleport.Client
*teleport.Client
ProxyAddress string
}

Expand All @@ -20,6 +22,10 @@ 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) {
if !hasPort(proxyAddress) {
proxyAddress += ":443"
}

tc := &TeleportClient{
ProxyAddress: proxyAddress,
}
Expand All @@ -36,58 +42,28 @@ 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
}

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://")
address = strings.TrimPrefix(address, "http://")
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))
}

func (t *TeleportClient) GetNodes(ctx context.Context) (*proto.ListResourcesResponse, error) {
return t.client.GetResources(ctx, &proto.ListResourcesRequest{
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,
})
}

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)
}
94 changes: 90 additions & 4 deletions pkg/connector/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func getNodeResource(node *Node) (*v2.Resource, error) {
rs.WithRoleProfile(map[string]interface{}{
"node_id": node.Id,
"node_name": node.Name,
"namespace": node.Namespace,
}),
},
)
Expand All @@ -51,8 +52,7 @@ 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)
nodes, err := n.client.GetNodes(ctx, token)
if err != nil {
return nil, "", nil, err
}
Expand All @@ -61,7 +61,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(),
}
}
Expand All @@ -75,7 +75,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) {
Expand All @@ -91,15 +91,101 @@ 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
// 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
// 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
//
// //
//
// // 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:
// 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
}
Expand Down
Loading

0 comments on commit 9041fbc

Please sign in to comment.