Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/types/databaseservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type DatabaseService interface {
GetNamespace() string

// GetResourceMatchers returns the resource matchers of the DatabaseService.
// Database services deployed by Teleport have known configurations where
// we will only define a single resource matcher.
GetResourceMatchers() []*DatabaseResourceMatcher
}

Expand Down
53 changes: 53 additions & 0 deletions lib/integrations/awsoidc/listdatabases.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,59 @@ func NewListDatabasesClient(ctx context.Context, req *AWSClientRequest) (ListDat
return newRDSClient(ctx, req)
}

// ListAllDatabases collects dbs until end of pages for all supported RDS engines and types.
func ListAllDatabases(ctx context.Context, clt ListDatabasesClient, region string) (*ListDatabasesResponse, error) {
fetchedRDSs := []types.Database{}

// Get all rds instances.
nextToken := ""
for {
resp, err := ListDatabases(ctx,
clt,
ListDatabasesRequest{
Region: region,
Engines: []string{services.RDSEngineMySQL, services.RDSEngineMariaDB, services.RDSEnginePostgres},
RDSType: rdsTypeInstance,
NextToken: nextToken,
},
)
if err != nil {
return nil, trace.Wrap(err)
}
fetchedRDSs = append(fetchedRDSs, resp.Databases...)
nextToken = resp.NextToken

if len(nextToken) == 0 {
break
}
}

// Get all rds clusters.
nextToken = ""
for {
resp, err := ListDatabases(ctx,
clt,
ListDatabasesRequest{
Region: region,
Engines: []string{services.RDSEngineAuroraMySQL, services.RDSEngineAuroraPostgres},
RDSType: rdsTypeCluster,
NextToken: nextToken,
},
)
if err != nil {
return nil, trace.Wrap(err)
}
fetchedRDSs = append(fetchedRDSs, resp.Databases...)
nextToken = resp.NextToken

if len(nextToken) == 0 {
break
}
}

return &ListDatabasesResponse{Databases: fetchedRDSs}, nil
}

// ListDatabases calls the following AWS API:
// https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBClusters.html
// https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBInstances.html
Expand Down
1 change: 1 addition & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ func (h *Handler) bindDefaultEndpoints() {
h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/ec2ice", h.WithClusterAuth(h.awsOIDCListEC2ICE))
h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/deployec2ice", h.WithClusterAuth(h.awsOIDCDeployEC2ICE))
h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/securitygroups", h.WithClusterAuth(h.awsOIDCListSecurityGroups))
h.POST("/webapi/sites/:site/integrations/aws-oidc/:name/requireddatabasesvpcs", h.WithClusterAuth(h.awsOIDCRequiredDatabasesVPCS))
h.GET("/webapi/scripts/integrations/configure/eice-iam.sh", h.WithLimiter(h.awsOIDCConfigureEICEIAM))

// AWS OIDC Integration specific endpoints:
Expand Down
117 changes: 117 additions & 0 deletions lib/web/integrations_awsoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@ import (
"context"
"fmt"
"net/http"
"slices"
"strings"

"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/aws"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/integrations/awsoidc"
"github.com/gravitational/teleport/lib/reversetunnelclient"
Expand Down Expand Up @@ -372,6 +377,118 @@ func (h *Handler) awsOIDCListSecurityGroups(w http.ResponseWriter, r *http.Reque
}, nil
}

// awsOIDCRequiredDatabasesVPCS returns a map of required VPC's and its subnets.
// This is required during the web UI discover flow (where users opt for auto
// discovery) to determine if user can skip the auto deployment screen (where we deploy
// database agents).
//
// This api will return empty if we already have agents that can proxy the discovered databases.
// Otherwise it will return with a map of VPC and its subnets where it's values are later used
// to configure and deploy an agent (deploy an agent per unique VPC).
func (h *Handler) awsOIDCRequiredDatabasesVPCS(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (any, error) {
ctx := r.Context()

var req ui.AWSOIDCRequiredVPCSRequest
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

awsClientReq, err := h.awsOIDCClientRequest(ctx, req.Region, p, sctx, site)
if err != nil {
return nil, trace.Wrap(err)
}

listDBsClient, err := awsoidc.NewListDatabasesClient(ctx, awsClientReq)
if err != nil {
return nil, trace.Wrap(err)
}

clt, err := sctx.GetUserClient(ctx, site)
if err != nil {
return nil, trace.Wrap(err)
}

resp, err := awsOIDCRequiredVPCSHelper(ctx, req, listDBsClient, clt)
if err != nil {
return nil, trace.Wrap(err)
}
return resp, nil
}

func awsOIDCRequiredVPCSHelper(ctx context.Context, req ui.AWSOIDCRequiredVPCSRequest, listDBsClient awsoidc.ListDatabasesClient, clt auth.ClientI) (*ui.AWSOIDCRequiredVPCSResponse, error) {
resp, err := awsoidc.ListAllDatabases(ctx, listDBsClient, req.Region)
if err != nil {
return nil, trace.Wrap(err)
}
if len(resp.Databases) == 0 {
return nil, trace.BadParameter("there are no available RDS instances or clusters found in region %q", req.Region)
}

// Get all database services with ecs/fargate metadata label.
nextToken := ""
fetchedDbSvcs := []types.DatabaseService{}
for {
page, err := client.GetResourcePage[types.DatabaseService](ctx, clt, &proto.ListResourcesRequest{
ResourceType: types.KindDatabaseService,
Limit: defaults.MaxIterationLimit,
StartKey: nextToken,
Labels: map[string]string{types.AWSOIDCAgentLabel: types.True},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We discussed this over slack, but this might not return the expected DB Services while the deployed version is <14.2.4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

are you suggesting that i remove it? (or just a FYI during testing?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FYI during testing.

I will try to create a dev build and maybe we can use that during our tests
It will be based on the current v14 branch which includes the new label

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can hard-code the following version if you want to check for the label

teleportVersionTag := "14.2.5-dev.marco.2" 

Example:
$ tctl get db_services

kind: db_service
metadata:
  expires: "2023-12-21T15:44:50.327704669Z"
  id: 1703172890371535865
  labels:
    teleport.dev/awsoidc-agent: "true"
  name: 05c13b7c-ed93-42de-9f43-753c1afc08f2
  revision: fba61218-27ea-45c9-ad81-3ff2384a7c1c
spec:
  resources:
  - aws: {}
    labels:
      account-id: "278576220453"
      region: us-east-1
      vpc-id: vpc-092abc
version: v1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

worked great 👍

lisakim ~/gravitational/teleport/e/build [master] $ ./tctl get db_services
kind: db_service
metadata:
  expires: "2023-12-22T04:55:30Z"
  id: 1703218345695400557
  labels:
    teleport.dev/awsoidc-agent: "true"
  name: 9cdde9be-9bd5-4c12-b203-363df6059e5c
spec:
  resources:
  - aws: {}
    labels:
      account-id: "278576220453"
      region: us-east-1
      vpc-id: vpc-02149278b986b6f83
version: v1
---
kind: db_service
metadata:
  expires: "2023-12-22T04:55:14Z"
  id: 1703218339966369543
  labels:
    teleport.dev/awsoidc-agent: "true"
  name: fb7abf52-a70b-4253-849a-da7ebe1cb92d
spec:
  resources:
  - aws: {}
    labels:
      account-id: "278576220453"
      region: us-east-1
      vpc-id: vpc-092c26a0e0e802e92
version: v1

})
if err != nil {
return nil, trace.Wrap(err)
}

fetchedDbSvcs = append(fetchedDbSvcs, page.Resources...)
nextToken = page.NextKey
if len(nextToken) == 0 {
break
}
}

// Construct map of VPCs and its subnets.
vpcLookup := map[string][]string{}
for _, db := range resp.Databases {
rds := db.GetAWS().RDS
vpcId := rds.VPCID
if _, found := vpcLookup[vpcId]; !found {
vpcLookup[vpcId] = rds.Subnets
continue
}
combinedSubnets := append(vpcLookup[vpcId], rds.Subnets...)
vpcLookup[vpcId] = utils.Deduplicate(combinedSubnets)
}

for _, svc := range fetchedDbSvcs {
if len(svc.GetResourceMatchers()) != 1 || svc.GetResourceMatchers()[0].Labels == nil {
continue
}

// Database services deployed by Teleport have known configurations where
// we will only define a single resource matcher.
labelMatcher := *svc.GetResourceMatchers()[0].Labels

// We check for length 3, because we are only
// wanting/checking for 3 discovery labels.
if len(labelMatcher) != 3 {
continue
}
if slices.Compare(labelMatcher[types.DiscoveryLabelAccountID], []string{req.AccountID}) != 0 {
continue
}
if slices.Compare(labelMatcher[types.DiscoveryLabelRegion], []string{req.Region}) != 0 {
continue
}
if len(labelMatcher[types.DiscoveryLabelVPCID]) != 1 {
continue
}
delete(vpcLookup, labelMatcher[types.DiscoveryLabelVPCID][0])
}

return &ui.AWSOIDCRequiredVPCSResponse{
VPCMapOfSubnets: vpcLookup,
}, nil
}

// awsOIDCListEC2ICE returns a list of EC2 Instance Connect Endpoints using the ListEC2ICE action of the AWS OIDC Integration.
func (h *Handler) awsOIDCListEC2ICE(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (any, error) {
ctx := r.Context()
Expand Down
Loading