Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix TODOs #24

Merged
merged 14 commits into from
Oct 28, 2024
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the || exit 0 here?

- 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
Loading