Skip to content

Commit d3ca4e5

Browse files
committed
Add container name to ECS ListTagsForResource request headers
Enhance CloudTrail visibility by including container name in HTTP User-Agent header when ECS agent makes ListTagsForResource API calls on behalf of containers. Changes: - Add ContainerNameByV3EndpointID method to TaskEngineState interface - Modify NewTaskResponse to accept v3EndpointID and pass container name to context - Update GetResourceTags to accept context parameter for container identification - Enhance ecsRoundTripper.userAgent to append container name from request context - Update TMDS v4 handler to pass endpoint ID through call chain - Add comprehensive unit tests for new functionality This enables tracing HTTP requests from containers to specific ECS API calls in CloudTrail logs, improving observability and debugging capabilities.
1 parent 6e9b8f3 commit d3ca4e5

File tree

22 files changed

+447
-100
lines changed

22 files changed

+447
-100
lines changed

agent/engine/dockerstate/docker_task_engine_state.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ type TaskEngineState interface {
8282
DockerIDByV3EndpointID(v3EndpointID string) (string, bool)
8383
// TaskARNByV3EndpointID returns a taskARN for a given v3 endpoint ID
8484
TaskARNByV3EndpointID(v3EndpointID string) (string, bool)
85+
// ContainerNameByV3EndpointID returns the container name for a given TMDS v3 endpoint ID
86+
ContainerNameByV3EndpointID(v3EndpointID string) (string, bool)
8587
// GetAllEBSAttachments returns all of the ebs attachments
8688
GetAllEBSAttachments() []*apiresource.ResourceAttachment
8789
// AllPendingEBSAttachments reutrns all of the ebs attachments that haven't sent a state change
@@ -713,3 +715,22 @@ func (state *DockerTaskEngineState) TaskARNByV3EndpointID(v3EndpointID string) (
713715
taskArn, ok := state.v3EndpointIDToTask[v3EndpointID]
714716
return taskArn, ok
715717
}
718+
719+
// ContainerNameByV3EndpointID returns the container name for a given TMDS v3 endpoint ID
720+
func (state *DockerTaskEngineState) ContainerNameByV3EndpointID(v3EndpointID string) (string, bool) {
721+
state.lock.RLock()
722+
defer state.lock.RUnlock()
723+
724+
dockerContainerID, ok := state.v3EndpointIDToDockerID[v3EndpointID]
725+
if !ok {
726+
return "", false
727+
}
728+
729+
dockerContainer, ok := state.idToContainer[dockerContainerID]
730+
if !ok {
731+
return "", false
732+
}
733+
734+
// Extract the name of the container specified in the ECS task definition
735+
return dockerContainer.Container.Name, true
736+
}

agent/engine/dockerstate/dockerstate_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,50 @@ func TestRemoveTaskRemoveV3EndpointID(t *testing.T) {
666666
_, ok = state.v3EndpointIDToDockerID["new-uuid-2"]
667667
assert.False(t, ok)
668668
}
669+
670+
// TestContainerNameByV3EndpointID tests that ContainerNameByV3EndpointID returns the correct container name
671+
func TestContainerNameByV3EndpointID(t *testing.T) {
672+
state := newDockerTaskEngineState()
673+
container1 := &apicontainer.Container{
674+
Name: "web-server",
675+
V3EndpointID: "endpoint-uuid-1",
676+
}
677+
dockerContainer1 := &apicontainer.DockerContainer{
678+
DockerID: "docker-id-1",
679+
Container: container1,
680+
}
681+
682+
container2 := &apicontainer.Container{
683+
Name: "sidecar-proxy",
684+
V3EndpointID: "endpoint-uuid-2",
685+
}
686+
dockerContainer2 := &apicontainer.DockerContainer{
687+
DockerID: "docker-id-2",
688+
Container: container2,
689+
}
690+
691+
task := &apitask.Task{
692+
Arn: "task-arn",
693+
Containers: []*apicontainer.Container{
694+
container1,
695+
container2,
696+
},
697+
}
698+
699+
state.AddTask(task)
700+
state.AddContainer(dockerContainer1, task)
701+
state.AddContainer(dockerContainer2, task)
702+
703+
// Test successful lookups
704+
containerName1, found1 := state.ContainerNameByV3EndpointID("endpoint-uuid-1")
705+
assert.True(t, found1)
706+
assert.Equal(t, "web-server", containerName1)
707+
708+
containerName2, found2 := state.ContainerNameByV3EndpointID("endpoint-uuid-2")
709+
assert.True(t, found2)
710+
assert.Equal(t, "sidecar-proxy", containerName2)
711+
712+
// Test lookup with non-existent endpoint ID
713+
_, found3 := state.ContainerNameByV3EndpointID("non-existent-uuid")
714+
assert.False(t, found3)
715+
}

agent/engine/dockerstate/mocks/dockerstate_mocks.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/handlers/task_server_setup_test.go

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,6 +2514,7 @@ func TestV2TaskMetadataWithTags(t *testing.T) {
25142514
state.EXPECT().GetTaskByIPAddress(remoteIP).Return(taskARN, true),
25152515
state.EXPECT().TaskByArn(taskARN).Return(task, true),
25162516
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2517+
state.EXPECT().ContainerNameByV3EndpointID("").Return(containerName, true),
25172518
)
25182519
}
25192520

@@ -2529,9 +2530,9 @@ func TestV2TaskMetadataWithTags(t *testing.T) {
25292530
setStateExpectations: happyStateExpectations,
25302531
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
25312532
gomock.InOrder(
2532-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).
2533+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).
25332534
Return(ecsInstanceTags, nil),
2534-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2535+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
25352536
)
25362537
},
25372538
expectedStatusCode: http.StatusOK,
@@ -2548,8 +2549,8 @@ func TestV2TaskMetadataWithTags(t *testing.T) {
25482549
setStateExpectations: happyStateExpectations,
25492550
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
25502551
gomock.InOrder(
2551-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(ecsInstanceTags, nil),
2552-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2552+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(ecsInstanceTags, nil),
2553+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
25532554
)
25542555
},
25552556
expectedStatusCode: http.StatusOK,
@@ -2564,8 +2565,8 @@ func TestV2TaskMetadataWithTags(t *testing.T) {
25642565
setStateExpectations: happyStateExpectations,
25652566
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
25662567
gomock.InOrder(
2567-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2568-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2568+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2569+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
25692570
)
25702571
},
25712572
expectedStatusCode: http.StatusOK,
@@ -2578,8 +2579,8 @@ func TestV2TaskMetadataWithTags(t *testing.T) {
25782579
setStateExpectations: happyStateExpectations,
25792580
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
25802581
gomock.InOrder(
2581-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2582-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2582+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2583+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
25832584
)
25842585
},
25852586
expectedStatusCode: http.StatusOK,
@@ -2644,15 +2645,16 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
26442645

26452646
happyECSClientExpectations := func(ecsClient *mock_ecs.MockECSClient) {
26462647
gomock.InOrder(
2647-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(ecsInstanceTags, nil),
2648-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2648+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(ecsInstanceTags, nil),
2649+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
26492650
)
26502651
}
26512652
happyStateExpectations := func(state *mock_dockerstate.MockTaskEngineState) {
26522653
gomock.InOrder(
26532654
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
26542655
state.EXPECT().TaskByArn(taskARN).Return(task, true),
26552656
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2657+
state.EXPECT().ContainerNameByV3EndpointID("").Return(containerName, true),
26562658
state.EXPECT().TaskByArn(taskARN).Return(task, true),
26572659
)
26582660
}
@@ -2677,8 +2679,8 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
26772679
setStateExpectations: happyStateExpectations,
26782680
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
26792681
gomock.InOrder(
2680-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2681-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2682+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2683+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
26822684
)
26832685
},
26842686
expectedStatusCode: http.StatusOK,
@@ -2693,8 +2695,8 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
26932695
setStateExpectations: happyStateExpectations,
26942696
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
26952697
gomock.InOrder(
2696-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(ecsInstanceTags, nil),
2697-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2698+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(ecsInstanceTags, nil),
2699+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
26982700
)
26992701
},
27002702
expectedStatusCode: http.StatusOK,
@@ -2707,8 +2709,8 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
27072709
setStateExpectations: happyStateExpectations,
27082710
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
27092711
gomock.InOrder(
2710-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2711-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2712+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2713+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
27122714
)
27132715
},
27142716
expectedStatusCode: http.StatusOK,
@@ -2768,6 +2770,7 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
27682770
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
27692771
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true),
27702772
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToBridgeContainer, true),
2773+
state.EXPECT().ContainerNameByV3EndpointID("").Return(containerName, true),
27712774
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true),
27722775
state.EXPECT().ContainerByID(containerID).Return(nil, false),
27732776
)
@@ -2785,6 +2788,7 @@ func TestV3TaskMetadataWithTags(t *testing.T) {
27852788
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
27862789
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true),
27872790
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToBridgeContainer, true),
2791+
state.EXPECT().ContainerNameByV3EndpointID("").Return(containerName, true),
27882792
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true),
27892793
state.EXPECT().ContainerByID(containerID).Return(bridgeContainerNoNetwork, true),
27902794
)
@@ -2818,8 +2822,8 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
28182822

28192823
happyECSClientExpectations := func(ecsClient *mock_ecs.MockECSClient) {
28202824
gomock.InOrder(
2821-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(ecsInstanceTags, nil),
2822-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2825+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(ecsInstanceTags, nil),
2826+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
28232827
)
28242828
}
28252829
happyStateExpectations := func(state *mock_dockerstate.MockTaskEngineState) {
@@ -2828,6 +2832,7 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
28282832
state.EXPECT().TaskByArn(taskARN).Return(task, true).AnyTimes(),
28292833
state.EXPECT().ContainerByID(containerID).Return(bridgeContainer, true).AnyTimes(),
28302834
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2835+
state.EXPECT().ContainerNameByV3EndpointID(v3EndpointID).Return(containerName, true),
28312836
state.EXPECT().TaskByArn(taskARN).Return(task, true).AnyTimes(),
28322837
state.EXPECT().ContainerByID(containerID).Return(bridgeContainer, true).AnyTimes(),
28332838
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
@@ -2857,8 +2862,8 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
28572862
setStateExpectations: happyStateExpectations,
28582863
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
28592864
gomock.InOrder(
2860-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2861-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(ecsTaskTags, nil),
2865+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2866+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(ecsTaskTags, nil),
28622867
)
28632868
},
28642869
expectedStatusCode: http.StatusOK,
@@ -2874,8 +2879,8 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
28742879
setStateExpectations: happyStateExpectations,
28752880
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
28762881
gomock.InOrder(
2877-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(ecsInstanceTags, nil),
2878-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2882+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(ecsInstanceTags, nil),
2883+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
28792884
)
28802885
},
28812886
expectedStatusCode: http.StatusOK,
@@ -2891,8 +2896,8 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
28912896
setStateExpectations: happyStateExpectations,
28922897
setECSClientExpectations: func(ecsClient *mock_ecs.MockECSClient) {
28932898
gomock.InOrder(
2894-
ecsClient.EXPECT().GetResourceTags(containerInstanceArn).Return(nil, errors.New("error")),
2895-
ecsClient.EXPECT().GetResourceTags(taskARN).Return(nil, errors.New("error")),
2899+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), containerInstanceArn).Return(nil, errors.New("error")),
2900+
ecsClient.EXPECT().GetResourceTags(gomock.Any(), taskARN).Return(nil, errors.New("error")),
28962901
)
28972902
},
28982903
expectedStatusCode: http.StatusOK,
@@ -2963,6 +2968,7 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
29632968
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
29642969
state.EXPECT().TaskByArn(taskARN).Return(task, true).Times(2),
29652970
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2971+
state.EXPECT().ContainerNameByV3EndpointID(v3EndpointID).Return(containerName, true),
29662972
state.EXPECT().TaskByArn(taskARN).Return(nil, false),
29672973
)
29682974
},
@@ -2983,6 +2989,7 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
29832989
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
29842990
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true).Times(2),
29852991
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToBridgeContainer, true),
2992+
state.EXPECT().ContainerNameByV3EndpointID(v3EndpointID).Return(containerName, true),
29862993
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
29872994
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
29882995
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
@@ -3004,6 +3011,7 @@ func TestV4TaskMetadataWithTags(t *testing.T) {
30043011
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
30053012
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true).Times(2),
30063013
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToBridgeContainer, true),
3014+
state.EXPECT().ContainerNameByV3EndpointID(v3EndpointID).Return(containerName, true),
30073015
state.EXPECT().ContainerByID(containerID).Return(bridgeContainerNoNetwork, true).AnyTimes(),
30083016
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
30093017
state.EXPECT().ContainerByID(containerID).Return(bridgeContainerNoNetwork, true).AnyTimes(),

agent/handlers/v2/response.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
package v2
1515

1616
import (
17+
"context"
18+
1719
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
1820
"github.com/aws/amazon-ecs-agent/agent/engine/dockerstate"
1921
v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1"
2022
apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status"
2123
"github.com/aws/amazon-ecs-agent/ecs-agent/api/ecs"
24+
"github.com/aws/amazon-ecs-agent/ecs-agent/httpclient"
2225
ni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
2326
tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response"
2427
"github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils"
@@ -37,6 +40,7 @@ const minimumCPUUnit = 2
3740

3841
// NewTaskResponse creates a new response object for the task
3942
func NewTaskResponse(
43+
endpointID string,
4044
taskARN string,
4145
state dockerstate.TaskEngineState,
4246
ecsClient ecs.ECSClient,
@@ -99,16 +103,23 @@ func NewTaskResponse(
99103
}
100104

101105
if propagateTags {
102-
propagateTagsToMetadata(ecsClient, containerInstanceArn, taskARN, resp, includeV4Metadata)
106+
propagateTagsToMetadata(endpointID, ecsClient, state, containerInstanceArn, taskARN, resp, includeV4Metadata)
103107
}
104108

105109
return resp, nil
106110
}
107111

108112
// propagateTagsToMetadata retrieves container instance and task tags from ECS
109-
func propagateTagsToMetadata(ecsClient ecs.ECSClient, containerInstanceARN, taskARN string, resp *tmdsv2.TaskResponse, includeV4Metadata bool) {
110-
containerInstanceTags, err := ecsClient.GetResourceTags(containerInstanceARN)
113+
func propagateTagsToMetadata(endpointID string, ecsClient ecs.ECSClient, state dockerstate.TaskEngineState, containerInstanceARN, taskARN string,
114+
resp *tmdsv2.TaskResponse, includeV4Metadata bool) {
115+
// Extract the container name and create a context for CloudTrail visibility
116+
ctx := context.Background()
117+
if containerName, found := state.ContainerNameByV3EndpointID(endpointID); found {
118+
ctx = context.WithValue(ctx, httpclient.ContainerNameKey, containerName)
119+
}
111120

121+
// Get container instance tags from ECS
122+
containerInstanceTags, err := ecsClient.GetResourceTags(ctx, containerInstanceARN)
112123
if err == nil {
113124
resp.ContainerInstanceTags = make(map[string]string)
114125
for _, tag := range containerInstanceTags {
@@ -118,7 +129,8 @@ func propagateTagsToMetadata(ecsClient ecs.ECSClient, containerInstanceARN, task
118129
metadataErrorHandling(resp, err, "ContainerInstanceTags", containerInstanceARN, includeV4Metadata)
119130
}
120131

121-
taskTags, err := ecsClient.GetResourceTags(taskARN)
132+
// Get task tags from ECS
133+
taskTags, err := ecsClient.GetResourceTags(ctx, taskARN)
122134
if err == nil {
123135
resp.TaskTags = make(map[string]string)
124136
for _, tag := range taskTags {

0 commit comments

Comments
 (0)