Skip to content

Commit

Permalink
Merge branch 'branch/v15' into bot/backport-36685-branch/v15
Browse files Browse the repository at this point in the history
  • Loading branch information
reedloden authored Jan 15, 2024
2 parents 40497d4 + 95dbfee commit 7d22c68
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 17 deletions.
28 changes: 22 additions & 6 deletions lib/integrations/awsoidc/list_ec2ice.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ type ListEC2ICERequest struct {
// Region is the region of the EICE.
Region string

// VPCID is the VPC to filter EC2 Instance Connect Endpoints.
VPCID string
// VPCIDs is a list of VPCs to filter EC2 Instance Connect Endpoints.
VPCIDs []string

// NextToken is the token to be used to fetch the next page.
// If empty, the first page is fetched.
Expand All @@ -48,8 +48,8 @@ func (req *ListEC2ICERequest) CheckAndSetDefaults() error {
return trace.BadParameter("region is required")
}

if req.VPCID == "" {
return trace.BadParameter("vpc id is required")
if len(req.VPCIDs) == 0 {
return trace.BadParameter("at least one vpc id is required")
}

return nil
Expand All @@ -75,13 +75,19 @@ type EC2InstanceConnectEndpoint struct {
// SubnetID is the subnet used by the endpoint.
// Please note that the Endpoint should be able to reach any subnet within the VPC.
SubnetID string `json:"subnetId,omitempty"`

// VPCID is the VPC ID where the Endpoint is created.
VPCID string `json:"vpcId,omitempty"`
}

// ListEC2ICEResponse contains a page of AWS EC2 Instances as Teleport Servers.
type ListEC2ICEResponse struct {
// EC2ICEs contains the page of EC2 Instance Connect Endpoint.
EC2ICEs []EC2InstanceConnectEndpoint `json:"ec2InstanceConnectEndpoints,omitempty"`

// DashboardLink is the URL for AWS Web Console that lists all the Endpoints for the queries VPCs.
DashboardLink string `json:"dashboardLink,omitempty"`

// NextToken is used for pagination.
// If non-empty, it can be used to request the next page.
NextToken string `json:"nextToken,omitempty"`
Expand Down Expand Up @@ -121,7 +127,7 @@ func ListEC2ICE(ctx context.Context, clt ListEC2ICEClient, req ListEC2ICERequest
describeEC2ICE := &ec2.DescribeInstanceConnectEndpointsInput{
Filters: []ec2Types.Filter{{
Name: aws.String("vpc-id"),
Values: []string{req.VPCID},
Values: req.VPCIDs,
}},
}
if req.NextToken != "" {
Expand All @@ -139,21 +145,31 @@ func ListEC2ICE(ctx context.Context, clt ListEC2ICEClient, req ListEC2ICERequest
ret.NextToken = *ec2ICEs.NextToken
}

ret.DashboardLink = fmt.Sprintf(
"https://%s.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=%s",
req.Region,
// We must use PathEscape here because it converts spaces into `%20`.
// QueryEscape converts spaces into `+`, which, when loaded in AWS Console page, filters for `EC2+Instance+Connect+Endpoint`, instead of `EC2 Instance Connect Endpoint`
url.PathEscape("EC2 Instance Connect Endpoint"),
)

ret.EC2ICEs = make([]EC2InstanceConnectEndpoint, 0, len(ec2ICEs.InstanceConnectEndpoints))
for _, ice := range ec2ICEs.InstanceConnectEndpoints {
name := aws.ToString(ice.InstanceConnectEndpointId)
subnetID := aws.ToString(ice.SubnetId)
vpcID := aws.ToString(ice.VpcId)
state := ice.State
stateMessage := aws.ToString(ice.StateMessage)

idURLSafe := url.QueryEscape(name)
dashboardLink := fmt.Sprintf("https://%s.console.aws.amazon.com/vpc/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=%s",
dashboardLink := fmt.Sprintf("https://%s.console.aws.amazon.com/vpcconsole/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=%s",
req.Region, idURLSafe,
)

ret.EC2ICEs = append(ret.EC2ICEs, EC2InstanceConnectEndpoint{
Name: name,
SubnetID: subnetID,
VPCID: vpcID,
State: string(state),
StateMessage: stateMessage,
DashboardLink: dashboardLink,
Expand Down
73 changes: 65 additions & 8 deletions lib/integrations/awsoidc/list_ec2ice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,36 +99,39 @@ func TestListEC2ICE(t *testing.T) {

// First page must return pageSize number of Endpoints
resp, err := ListEC2ICE(ctx, mockListClient, ListEC2ICERequest{
VPCID: "vpc-123",
VPCIDs: []string{"vpc-123"},
Region: "us-east-1",
NextToken: "",
})
require.NoError(t, err)
require.NotEmpty(t, resp.NextToken)
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", resp.DashboardLink)
require.Len(t, resp.EC2ICEs, pageSize)
nextPageToken := resp.NextToken
require.Equal(t, "subnet-0", resp.EC2ICEs[0].SubnetID)

// Second page must return pageSize number of Endpoints
resp, err = ListEC2ICE(ctx, mockListClient, ListEC2ICERequest{
VPCID: "vpc-abc",
VPCIDs: []string{"vpc-abc"},
Region: "us-east-1",
NextToken: nextPageToken,
})
require.NoError(t, err)
require.NotEmpty(t, resp.NextToken)
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", resp.DashboardLink)
require.Len(t, resp.EC2ICEs, pageSize)
nextPageToken = resp.NextToken
require.Equal(t, "subnet-100", resp.EC2ICEs[0].SubnetID)

// Third page must return only the remaining Endpoints and an empty nextToken
resp, err = ListEC2ICE(ctx, mockListClient, ListEC2ICERequest{
VPCID: "vpc-abc",
VPCIDs: []string{"vpc-abc"},
Region: "us-east-1",
NextToken: nextPageToken,
})
require.NoError(t, err)
require.Empty(t, resp.NextToken)
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", resp.DashboardLink)
require.Len(t, resp.EC2ICEs, 3)
require.Equal(t, "subnet-200", resp.EC2ICEs[0].SubnetID)
})
Expand All @@ -143,27 +146,30 @@ func TestListEC2ICE(t *testing.T) {
{
name: "valid for listing endpoints",
req: ListEC2ICERequest{
VPCID: "vpc-abcd",
VPCIDs: []string{"vpc-abcd"},
Region: "us-east-1",
NextToken: "",
},
mockEndpoints: []ec2Types.Ec2InstanceConnectEndpoint{{
SubnetId: aws.String("subnet-123"),
VpcId: aws.String("vpc-abcd"),
InstanceConnectEndpointId: aws.String("ice-name"),
State: "create-complete",
StateMessage: aws.String("success message"),
},
},
respCheck: func(t *testing.T, ldr *ListEC2ICEResponse) {
require.Len(t, ldr.EC2ICEs, 1, "expected 1 endpoint, got %d", len(ldr.EC2ICEs))
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", ldr.DashboardLink)
require.Empty(t, ldr.NextToken, "expected an empty NextToken")

endpoint := EC2InstanceConnectEndpoint{
Name: "ice-name",
State: "create-complete",
SubnetID: "subnet-123",
VPCID: "vpc-abcd",
StateMessage: "success message",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpc/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice-name",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice-name",
}
require.Empty(t, cmp.Diff(endpoint, ldr.EC2ICEs[0]))
},
Expand All @@ -172,7 +178,7 @@ func TestListEC2ICE(t *testing.T) {
{
name: "valid but ID needs URL encoding",
req: ListEC2ICERequest{
VPCID: "vpc-abcd",
VPCIDs: []string{"vpc-abcd"},
Region: "us-east-1",
NextToken: "",
},
Expand All @@ -185,19 +191,70 @@ func TestListEC2ICE(t *testing.T) {
},
respCheck: func(t *testing.T, ldr *ListEC2ICEResponse) {
require.Len(t, ldr.EC2ICEs, 1, "expected 1 endpoint, got %d", len(ldr.EC2ICEs))
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", ldr.DashboardLink)
require.Empty(t, ldr.NextToken, "expected an empty NextToken")

endpoint := EC2InstanceConnectEndpoint{
Name: "ice/123",
State: "create-complete",
SubnetID: "subnet-123",
StateMessage: "success message",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpc/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice%2F123",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice%2F123",
}
require.Empty(t, cmp.Diff(endpoint, ldr.EC2ICEs[0]))
},
errCheck: noErrorFunc,
},
{
name: "valid for multiple VPCs",
req: ListEC2ICERequest{
VPCIDs: []string{"vpc-01", "vpc-02"},
Region: "us-east-1",
NextToken: "",
},
mockEndpoints: []ec2Types.Ec2InstanceConnectEndpoint{
{
SubnetId: aws.String("subnet-123"),
VpcId: aws.String("vpc-01"),
InstanceConnectEndpointId: aws.String("ice-name-1"),
State: "create-complete",
StateMessage: aws.String("success message"),
},
{
SubnetId: aws.String("subnet-123"),
VpcId: aws.String("vpc-02"),
InstanceConnectEndpointId: aws.String("ice-name-2"),
State: "create-complete",
StateMessage: aws.String("success message"),
},
},
respCheck: func(t *testing.T, ldr *ListEC2ICEResponse) {
require.Len(t, ldr.EC2ICEs, 2, "expected 1 endpoint, got %d", len(ldr.EC2ICEs))
require.Equal(t, "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#Endpoints:v=3;vpcEndpointType=EC2%20Instance%20Connect%20Endpoint", ldr.DashboardLink)
require.Empty(t, ldr.NextToken, "expected an empty NextToken")

endpoint := EC2InstanceConnectEndpoint{
Name: "ice-name-1",
State: "create-complete",
SubnetID: "subnet-123",
VPCID: "vpc-01",
StateMessage: "success message",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice-name-1",
}
require.Empty(t, cmp.Diff(endpoint, ldr.EC2ICEs[0]))

endpoint = EC2InstanceConnectEndpoint{
Name: "ice-name-2",
State: "create-complete",
SubnetID: "subnet-123",
VPCID: "vpc-02",
StateMessage: "success message",
DashboardLink: "https://us-east-1.console.aws.amazon.com/vpcconsole/home?#InstanceConnectEndpointDetails:instanceConnectEndpointId=ice-name-2",
}
require.Empty(t, cmp.Diff(endpoint, ldr.EC2ICEs[1]))
},
errCheck: noErrorFunc,
},
{
name: "no vpc id",
req: ListEC2ICERequest{
Expand All @@ -208,7 +265,7 @@ func TestListEC2ICE(t *testing.T) {
{
name: "no region id",
req: ListEC2ICERequest{
VPCID: "vpc-123",
VPCIDs: []string{"vpc-123"},
},
errCheck: trace.IsBadParameter,
},
Expand Down
12 changes: 9 additions & 3 deletions lib/web/integrations_awsoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,11 +642,16 @@ func (h *Handler) awsOIDCListEC2ICE(w http.ResponseWriter, r *http.Request, p ht
return nil, trace.Wrap(err)
}

vpcIds := req.VPCIDs
if len(vpcIds) == 0 {
vpcIds = []string{req.VPCID}
}

resp, err := awsoidc.ListEC2ICE(ctx,
listEC2ICEClient,
awsoidc.ListEC2ICERequest{
Region: req.Region,
VPCID: req.VPCID,
VPCIDs: vpcIds,
NextToken: req.NextToken,
},
)
Expand All @@ -655,8 +660,9 @@ func (h *Handler) awsOIDCListEC2ICE(w http.ResponseWriter, r *http.Request, p ht
}

return ui.AWSOIDCListEC2ICEResponse{
NextToken: resp.NextToken,
EC2ICEs: resp.EC2ICEs,
NextToken: resp.NextToken,
DashboardLink: resp.DashboardLink,
EC2ICEs: resp.EC2ICEs,
}, nil
}

Expand Down
6 changes: 6 additions & 0 deletions lib/web/ui/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,10 @@ type AWSOIDCListEC2ICERequest struct {
// Region is the AWS Region.
Region string `json:"region"`
// VPCID is the VPC to filter EC2 Instance Connect Endpoints.
// Deprecated: use VPCIDs instead.
VPCID string `json:"vpcId"`
// VPCIDs is a list of VPCs to filter EC2 Instance Connect Endpoints.
VPCIDs []string `json:"vpcIds"`
// NextToken is the token to be used to fetch the next page.
// If empty, the first page is fetched.
NextToken string `json:"nextToken"`
Expand All @@ -324,6 +327,9 @@ type AWSOIDCListEC2ICEResponse struct {
// EC2ICEs contains the page of Endpoints
EC2ICEs []awsoidc.EC2InstanceConnectEndpoint `json:"ec2Ices"`

// DashboardLink is the URL for AWS Web Console that lists all the Endpoints for the queries VPCs.
DashboardLink string `json:"dashboardLink,omitempty"`

// NextToken is used for pagination.
// If non-empty, it can be used to request the next page.
NextToken string `json:"nextToken,omitempty"`
Expand Down

0 comments on commit 7d22c68

Please sign in to comment.