From 3de16335a009bf19a23971ca69c97cb10bc02379 Mon Sep 17 00:00:00 2001 From: Soo Oh Date: Wed, 10 May 2023 20:00:15 -0400 Subject: [PATCH 1/9] Add task manifest/stop verification ACK common interface to ecs-agent/ --- ecs-agent/acs/session/task_manifest_common.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 ecs-agent/acs/session/task_manifest_common.go diff --git a/ecs-agent/acs/session/task_manifest_common.go b/ecs-agent/acs/session/task_manifest_common.go new file mode 100644 index 00000000000..896583fdb82 --- /dev/null +++ b/ecs-agent/acs/session/task_manifest_common.go @@ -0,0 +1,9 @@ +package session + +// ManifestMessageIDAccessor stores the latest message ID that corresponds to the +// most recently processed task manifest message and is used to determine the +// validity of a task stop verification ack message. +type ManifestMessageIDAccessor interface { + GetMessageID() string + SetMessageID(messageID string) error +} From ed885c0d74413cfb6f8ac46efff0b8a1c7989fd3 Mon Sep 17 00:00:00 2001 From: Amogh Rathore Date: Mon, 15 May 2023 09:29:51 -0700 Subject: [PATCH 2/9] Fix Windows CI fail fast (#3694) --- .github/workflows/windows.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3264a418e9c..cf909989624 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -31,7 +31,7 @@ jobs: with: submodules: true path: src/github.com/aws/amazon-ecs-agent - - name: run tests + - name: run agent tests working-directory: run: | $Env:GOPATH = "$Env:GITHUB_WORKSPACE" @@ -41,6 +41,12 @@ jobs: $env:ZZZ_SKIP_WINDOWS_SERVER_VERSION_CHECK_NOT_SUPPORTED_IN_PRODUCTION = 'true' $packages=go list .\... | Where-Object {$_ -NotMatch 'vendor'} go test -v -tags unit -timeout=120s $packages - cd ../ecs-agent + - name: run ecs-agent tests + working-directory: + run: | + $Env:GOPATH = "$Env:GITHUB_WORKSPACE" + cd "$Env:GITHUB_WORKSPACE" + cd "src/github.com/aws/amazon-ecs-agent/ecs-agent" + gcc --version $packages=go list .\... | Where-Object {$_ -NotMatch 'vendor'} go test -v -tags unit -timeout=120s $packages From 0a7052360d14184c84d150f535e86cbca8b3115b Mon Sep 17 00:00:00 2001 From: Amogh Rathore Date: Mon, 15 May 2023 13:37:29 -0700 Subject: [PATCH 3/9] Move handlers utils to ecs-agent (#3695) --- .../taskprotection/v1/handlers/handlers.go | 2 +- agent/handlers/task_server_setup_test.go | 2 +- agent/handlers/v1/agent_metadata_handler.go | 2 +- agent/handlers/v1/credentials_handler.go | 2 +- agent/handlers/v1/response.go | 2 +- .../v1/task_container_metadata_handler.go | 5 +- agent/handlers/v2/credentials_handler.go | 2 +- agent/handlers/v2/response.go | 2 +- .../v2/task_container_metadata_handler.go | 2 +- .../v2/task_container_stats_handler.go | 2 +- .../v3/container_association_handler.go | 2 +- .../handlers/v3/container_metadata_handler.go | 2 +- agent/handlers/v3/container_stats_handler.go | 2 +- agent/handlers/v3/helper.go | 2 +- agent/handlers/v3/task_metadata_handler.go | 2 +- agent/handlers/v3/task_stats_handler.go | 2 +- .../v4/container_association_handler.go | 2 +- .../handlers/v4/container_metadata_handler.go | 2 +- agent/handlers/v4/container_stats_handler.go | 2 +- agent/handlers/v4/response.go | 2 +- agent/handlers/v4/task_metadata_handler.go | 2 +- agent/handlers/v4/task_stats_handler.go | 2 +- .../ecs-agent/tmds}/handlers/utils/helpers.go | 0 .../amazon-ecs-agent/ecs-agent/tmds/server.go | 14 +- agent/vendor/modules.txt | 1 + ecs-agent/tmds/handlers/utils/helpers.go | 135 ++++++++++++++++++ .../tmds}/handlers/utils/helpers_test.go | 22 +++ ecs-agent/tmds/server.go | 14 +- 28 files changed, 186 insertions(+), 47 deletions(-) rename agent/{ => vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds}/handlers/utils/helpers.go (100%) create mode 100644 ecs-agent/tmds/handlers/utils/helpers.go rename {agent => ecs-agent/tmds}/handlers/utils/helpers_test.go (72%) diff --git a/agent/handlers/agentapi/taskprotection/v1/handlers/handlers.go b/agent/handlers/agentapi/taskprotection/v1/handlers/handlers.go index 774b2674b4d..32f32eedf99 100644 --- a/agent/handlers/agentapi/taskprotection/v1/handlers/handlers.go +++ b/agent/handlers/agentapi/taskprotection/v1/handlers/handlers.go @@ -27,12 +27,12 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" "github.com/aws/amazon-ecs-agent/agent/handlers/agentapi/taskprotection/v1/types" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" "github.com/aws/amazon-ecs-agent/agent/httpclient" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" "github.com/aws/amazon-ecs-agent/ecs-agent/logger" loggerfield "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" awscreds "github.com/aws/aws-sdk-go/aws/credentials" diff --git a/agent/handlers/task_server_setup_test.go b/agent/handlers/task_server_setup_test.go index 78969ca10e4..18692462333 100644 --- a/agent/handlers/task_server_setup_test.go +++ b/agent/handlers/task_server_setup_test.go @@ -39,7 +39,6 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" mock_dockerstate "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate/mocks" task_protection_v1 "github.com/aws/amazon-ecs-agent/agent/handlers/agentapi/taskprotection/v1/handlers" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" @@ -51,6 +50,7 @@ import ( "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" mock_credentials "github.com/aws/amazon-ecs-agent/ecs-agent/credentials/mocks" mock_audit "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/aws-sdk-go/aws" "github.com/docker/docker/api/types" "github.com/golang/mock/gomock" diff --git a/agent/handlers/v1/agent_metadata_handler.go b/agent/handlers/v1/agent_metadata_handler.go index 8a026281e17..8d962645c1a 100644 --- a/agent/handlers/v1/agent_metadata_handler.go +++ b/agent/handlers/v1/agent_metadata_handler.go @@ -18,8 +18,8 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/config" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" agentversion "github.com/aws/amazon-ecs-agent/agent/version" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" ) // AgentMetadataPath is the Agent metadata path for v1 handler. diff --git a/agent/handlers/v1/credentials_handler.go b/agent/handlers/v1/credentials_handler.go index 61ca00e13cf..080b6635882 100644 --- a/agent/handlers/v1/credentials_handler.go +++ b/agent/handlers/v1/credentials_handler.go @@ -19,12 +19,12 @@ import ( "fmt" "net/http" - handlersutils "github.com/aws/amazon-ecs-agent/agent/handlers/utils" "github.com/aws/amazon-ecs-agent/agent/logger/audit" "github.com/aws/amazon-ecs-agent/agent/utils" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + handlersutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v1/response.go b/agent/handlers/v1/response.go index 7d14f16b4a1..43ce3a55aaa 100644 --- a/agent/handlers/v1/response.go +++ b/agent/handlers/v1/response.go @@ -18,8 +18,8 @@ import ( apitask "github.com/aws/amazon-ecs-agent/agent/api/task" "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" ) // MetadataResponse is the schema for the metadata response JSON object diff --git a/agent/handlers/v1/task_container_metadata_handler.go b/agent/handlers/v1/task_container_metadata_handler.go index 830c68df0c3..dc51cfd0ef6 100644 --- a/agent/handlers/v1/task_container_metadata_handler.go +++ b/agent/handlers/v1/task_container_metadata_handler.go @@ -20,6 +20,7 @@ import ( apitask "github.com/aws/amazon-ecs-agent/agent/api/task" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" "github.com/aws/amazon-ecs-agent/agent/handlers/utils" + commonutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) @@ -61,8 +62,8 @@ func TaskContainerMetadataHandler(taskEngine utils.DockerStateResolver) func(htt var err error var responseJSON []byte dockerTaskEngineState := taskEngine.State() - dockerID, dockerIDExists := utils.ValueFromRequest(r, dockerIDQueryField) - taskArn, taskARNExists := utils.ValueFromRequest(r, taskARNQueryField) + dockerID, dockerIDExists := commonutils.ValueFromRequest(r, dockerIDQueryField) + taskArn, taskARNExists := commonutils.ValueFromRequest(r, taskARNQueryField) var status int if dockerIDExists && taskARNExists { seelog.Info("Request contains both ", dockerIDQueryField, " and ", taskARNQueryField, ". Expect at most one of these.") diff --git a/agent/handlers/v2/credentials_handler.go b/agent/handlers/v2/credentials_handler.go index 0b37ab01625..fbafa14a55c 100644 --- a/agent/handlers/v2/credentials_handler.go +++ b/agent/handlers/v2/credentials_handler.go @@ -17,10 +17,10 @@ import ( "fmt" "net/http" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/gorilla/mux" ) diff --git a/agent/handlers/v2/response.go b/agent/handlers/v2/response.go index ae50f3142d5..f80c4dc39a7 100644 --- a/agent/handlers/v2/response.go +++ b/agent/handlers/v2/response.go @@ -22,9 +22,9 @@ import ( apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/aws-sdk-go/aws" "github.com/cihub/seelog" "github.com/pkg/errors" diff --git a/agent/handlers/v2/task_container_metadata_handler.go b/agent/handlers/v2/task_container_metadata_handler.go index 90e38d9d2f1..5a72c06a2a2 100644 --- a/agent/handlers/v2/task_container_metadata_handler.go +++ b/agent/handlers/v2/task_container_metadata_handler.go @@ -20,7 +20,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/api" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v2/task_container_stats_handler.go b/agent/handlers/v2/task_container_stats_handler.go index 65f21da9e3d..377543e394f 100644 --- a/agent/handlers/v2/task_container_stats_handler.go +++ b/agent/handlers/v2/task_container_stats_handler.go @@ -19,8 +19,8 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" "github.com/aws/amazon-ecs-agent/agent/stats" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v3/container_association_handler.go b/agent/handlers/v3/container_association_handler.go index fbaa2d5e6bc..1f76c3be253 100644 --- a/agent/handlers/v3/container_association_handler.go +++ b/agent/handlers/v3/container_association_handler.go @@ -19,7 +19,7 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v3/container_metadata_handler.go b/agent/handlers/v3/container_metadata_handler.go index f247f2c9a22..7860f57a199 100644 --- a/agent/handlers/v3/container_metadata_handler.go +++ b/agent/handlers/v3/container_metadata_handler.go @@ -20,8 +20,8 @@ import ( "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" "github.com/pkg/errors" ) diff --git a/agent/handlers/v3/container_stats_handler.go b/agent/handlers/v3/container_stats_handler.go index 3492cbe46f1..add7f9ecdf8 100644 --- a/agent/handlers/v3/container_stats_handler.go +++ b/agent/handlers/v3/container_stats_handler.go @@ -19,9 +19,9 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" "github.com/aws/amazon-ecs-agent/agent/stats" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v3/helper.go b/agent/handlers/v3/helper.go index 1825fe7f570..154e0307f75 100644 --- a/agent/handlers/v3/helper.go +++ b/agent/handlers/v3/helper.go @@ -17,7 +17,7 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/pkg/errors" ) diff --git a/agent/handlers/v3/task_metadata_handler.go b/agent/handlers/v3/task_metadata_handler.go index 5a7c007529a..8d3d328a800 100644 --- a/agent/handlers/v3/task_metadata_handler.go +++ b/agent/handlers/v3/task_metadata_handler.go @@ -20,8 +20,8 @@ import ( "github.com/aws/amazon-ecs-agent/agent/api" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v3/task_stats_handler.go b/agent/handlers/v3/task_stats_handler.go index 763855c69f1..b5c4e4d3c2f 100644 --- a/agent/handlers/v3/task_stats_handler.go +++ b/agent/handlers/v3/task_stats_handler.go @@ -19,9 +19,9 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" "github.com/aws/amazon-ecs-agent/agent/stats" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v4/container_association_handler.go b/agent/handlers/v4/container_association_handler.go index 848b54a006a..3050309157f 100644 --- a/agent/handlers/v4/container_association_handler.go +++ b/agent/handlers/v4/container_association_handler.go @@ -21,7 +21,7 @@ import ( v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v4/container_metadata_handler.go b/agent/handlers/v4/container_metadata_handler.go index 1e8b14a5fb1..529147c52bd 100644 --- a/agent/handlers/v4/container_metadata_handler.go +++ b/agent/handlers/v4/container_metadata_handler.go @@ -20,8 +20,8 @@ import ( "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" "github.com/pkg/errors" ) diff --git a/agent/handlers/v4/container_stats_handler.go b/agent/handlers/v4/container_stats_handler.go index e0e2fb8dcd5..09ceb81916f 100644 --- a/agent/handlers/v4/container_stats_handler.go +++ b/agent/handlers/v4/container_stats_handler.go @@ -19,9 +19,9 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" "github.com/aws/amazon-ecs-agent/agent/stats" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v4/response.go b/agent/handlers/v4/response.go index 83c5fde1a20..e0a2d4b17a8 100644 --- a/agent/handlers/v4/response.go +++ b/agent/handlers/v4/response.go @@ -19,9 +19,9 @@ import ( apitask "github.com/aws/amazon-ecs-agent/agent/api/task" "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/pkg/errors" ) diff --git a/agent/handlers/v4/task_metadata_handler.go b/agent/handlers/v4/task_metadata_handler.go index d73c0f30c00..e00743c3e92 100644 --- a/agent/handlers/v4/task_metadata_handler.go +++ b/agent/handlers/v4/task_metadata_handler.go @@ -20,8 +20,8 @@ import ( "github.com/aws/amazon-ecs-agent/agent/api" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/v4/task_stats_handler.go b/agent/handlers/v4/task_stats_handler.go index 0a0023b4971..fd26d88b65e 100644 --- a/agent/handlers/v4/task_stats_handler.go +++ b/agent/handlers/v4/task_stats_handler.go @@ -19,9 +19,9 @@ import ( "net/http" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" - "github.com/aws/amazon-ecs-agent/agent/handlers/utils" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" "github.com/aws/amazon-ecs-agent/agent/stats" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" ) diff --git a/agent/handlers/utils/helpers.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils/helpers.go similarity index 100% rename from agent/handlers/utils/helpers.go rename to agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils/helpers.go diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/server.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/server.go index 197c3307c1a..0d72f659de0 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/server.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/server.go @@ -19,7 +19,7 @@ import ( "time" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" - "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging" muxutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/utils/mux" @@ -111,7 +111,7 @@ func setup(auditLogger audit.AuditLogger, config *Config) (*http.Server, error) // Define a reqeuest rate limiter limiter := tollbooth. NewLimiter(config.steadyStateRate, nil). - SetOnLimitReached(limitReachedHandler(auditLogger)). + SetOnLimitReached(utils.LimitReachedHandler(auditLogger)). SetBurst(config.burstRate) // Log all requests and then pass through to muxRouter. @@ -132,13 +132,3 @@ func setup(auditLogger audit.AuditLogger, config *Config) (*http.Server, error) WriteTimeout: config.writeTimeout, }, nil } - -// LimitReachedHandler logs the throttled request in the credentials audit log -func limitReachedHandler(auditLogger audit.AuditLogger) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - logRequest := request.LogRequest{ - Request: r, - } - auditLogger.Log(logRequest, http.StatusTooManyRequests, "") - } -} diff --git a/agent/vendor/modules.txt b/agent/vendor/modules.txt index 2e82d3ddb2c..1a121666712 100644 --- a/agent/vendor/modules.txt +++ b/agent/vendor/modules.txt @@ -21,6 +21,7 @@ github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request github.com/aws/amazon-ecs-agent/ecs-agent/logger/field github.com/aws/amazon-ecs-agent/ecs-agent/tmds +github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging github.com/aws/amazon-ecs-agent/ecs-agent/tmds/utils/mux github.com/aws/amazon-ecs-agent/ecs-agent/utils/arn diff --git a/ecs-agent/tmds/handlers/utils/helpers.go b/ecs-agent/tmds/handlers/utils/helpers.go new file mode 100644 index 00000000000..7e9b23b71cc --- /dev/null +++ b/ecs-agent/tmds/handlers/utils/helpers.go @@ -0,0 +1,135 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package utils + +import ( + "fmt" + "net/http" + + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + "github.com/cihub/seelog" + "github.com/gorilla/mux" +) + +const ( + // NetworkModeAWSVPC specifies the AWS VPC network mode. + NetworkModeAWSVPC = "awsvpc" + + // RequestTypeCreds specifies the request type of CredentialsHandler. + RequestTypeCreds = "credentials" + + // RequestTypeTaskMetadata specifies the task metadata request type of TaskContainerMetadataHandler. + RequestTypeTaskMetadata = "task metadata" + + // RequestTypeContainerMetadata specifies the container metadata request type of TaskContainerMetadataHandler. + RequestTypeContainerMetadata = "container metadata" + + // RequestTypeTaskStats specifies the task stats request type of StatsHandler. + RequestTypeTaskStats = "task stats" + + // RequestTypeContainerStats specifies the container stats request type of StatsHandler. + RequestTypeContainerStats = "container stats" + + // RequestTypeAgentMetadata specifies the Agent metadata request type of AgentMetadataHandler. + RequestTypeAgentMetadata = "agent metadata" + + // RequestTypeContainerAssociations specifies the container associations request type of ContainerAssociationsHandler. + RequestTypeContainerAssociations = "container associations" + + // RequestTypeContainerAssociation specifies the container association request type of ContainerAssociationHandler. + RequestTypeContainerAssociation = "container association" + + // AnythingButSlashRegEx is a regex pattern that matches any string without slash. + AnythingButSlashRegEx = "[^/]*" + + // AnythingRegEx is a regex pattern that matches anything. + AnythingRegEx = ".*" + + // AnythingButEmptyRegEx is a regex pattern that matches anything but an empty string. + AnythingButEmptyRegEx = ".+" +) + +// ErrorMessage is used to store the human-readable error Code and a descriptive Message +// that describes the error. This struct is marshalled and returned in the HTTP response. +type ErrorMessage struct { + Code string `json:"code"` + Message string `json:"message"` + HTTPErrorCode int +} + +// WriteJSONToResponse writes the header, JSON response to a ResponseWriter, and +// log the error if necessary. +func WriteJSONToResponse(w http.ResponseWriter, httpStatusCode int, responseJSON []byte, requestType string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(httpStatusCode) + _, err := w.Write(responseJSON) + if err != nil { + seelog.Errorf( + "Unable to write %s json response message to ResponseWriter", + requestType) + } + + if httpStatusCode >= 400 && httpStatusCode <= 599 { + seelog.Errorf("HTTP response status code is '%d', request type is: %s, and response in JSON is %s", httpStatusCode, requestType, string(responseJSON)) + } +} + +// WriteResponseIfMarshalError checks the 'err' response of the json.Marshal function. +// if this function returns an error, then it has already written a response to the +// http writer, and the calling function should return. +func WriteResponseIfMarshalError(w http.ResponseWriter, err error) error { + if err != nil { + WriteJSONToResponse(w, http.StatusInternalServerError, []byte(`{}`), RequestTypeAgentMetadata) + seelog.Errorf("Error marshaling json: %s", err) + return fmt.Errorf("json marshal error") + } + return nil +} + +// ValueFromRequest returns the value of a field in the http request. The boolean value is +// set to true if the field exists in the query. +func ValueFromRequest(r *http.Request, field string) (string, bool) { + values := r.URL.Query() + _, exists := values[field] + return values.Get(field), exists +} + +// GetMuxValueFromRequest extracts the mux value from the request using a gorilla +// mux name +func GetMuxValueFromRequest(r *http.Request, gorillaMuxName string) (string, bool) { + vars := mux.Vars(r) + val, ok := vars[gorillaMuxName] + return val, ok +} + +// ConstructMuxVar constructs the mux var that is used in the gorilla/mux styled +// path, example: {id}, {id:[0-9]+}. +func ConstructMuxVar(name string, pattern string) string { + if pattern == "" { + return "{" + name + "}" + } + + return "{" + name + ":" + pattern + "}" +} + +// LimitReachedHandler logs the throttled request in the credentials audit log +func LimitReachedHandler(auditLogger audit.AuditLogger) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + logRequest := request.LogRequest{ + Request: r, + } + auditLogger.Log(logRequest, http.StatusTooManyRequests, "") + } +} diff --git a/agent/handlers/utils/helpers_test.go b/ecs-agent/tmds/handlers/utils/helpers_test.go similarity index 72% rename from agent/handlers/utils/helpers_test.go rename to ecs-agent/tmds/handlers/utils/helpers_test.go index 7be7c9c5d0d..56ca5390d60 100644 --- a/agent/handlers/utils/helpers_test.go +++ b/ecs-agent/tmds/handlers/utils/helpers_test.go @@ -23,7 +23,11 @@ import ( "net/http/httptest" "testing" + mock_audit "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestConstructMuxVar(t *testing.T) { @@ -86,3 +90,21 @@ func TestValueFromRequest(t *testing.T) { assert.True(t, ok) assert.Equal(t, "credid", val) } + +// Tests that an audit log is created by LimitReachHandler +func TestLimitReachHandler(t *testing.T) { + // Prepare a request + req, err := http.NewRequest("GET", "/endpoint", nil) + require.NoError(t, err) + + // Set up mock audit logger with a log expectation + ctrl := gomock.NewController(t) + defer ctrl.Finish() + auditLogger := mock_audit.NewMockAuditLogger(ctrl) + auditLogger.EXPECT().Log(request.LogRequest{Request: req}, http.StatusTooManyRequests, "") + + // Send the request, assertion is performed by the expectation on the mock audit logger + handler := http.HandlerFunc(LimitReachedHandler(auditLogger)) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) +} diff --git a/ecs-agent/tmds/server.go b/ecs-agent/tmds/server.go index 197c3307c1a..0d72f659de0 100644 --- a/ecs-agent/tmds/server.go +++ b/ecs-agent/tmds/server.go @@ -19,7 +19,7 @@ import ( "time" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" - "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging" muxutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/utils/mux" @@ -111,7 +111,7 @@ func setup(auditLogger audit.AuditLogger, config *Config) (*http.Server, error) // Define a reqeuest rate limiter limiter := tollbooth. NewLimiter(config.steadyStateRate, nil). - SetOnLimitReached(limitReachedHandler(auditLogger)). + SetOnLimitReached(utils.LimitReachedHandler(auditLogger)). SetBurst(config.burstRate) // Log all requests and then pass through to muxRouter. @@ -132,13 +132,3 @@ func setup(auditLogger audit.AuditLogger, config *Config) (*http.Server, error) WriteTimeout: config.writeTimeout, }, nil } - -// LimitReachedHandler logs the throttled request in the credentials audit log -func limitReachedHandler(auditLogger audit.AuditLogger) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - logRequest := request.LogRequest{ - Request: r, - } - auditLogger.Log(logRequest, http.StatusTooManyRequests, "") - } -} From 6677f5a1f34759c2f7d3504e03c21a995c1f28ee Mon Sep 17 00:00:00 2001 From: Amogh Rathore Date: Tue, 16 May 2023 09:47:06 -0700 Subject: [PATCH 4/9] Move versioning and encryption properties for AuditLogsBucket from bucket policy to bucket resource (#3699) --- build-infrastructure/audit-logs-stack.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build-infrastructure/audit-logs-stack.yml b/build-infrastructure/audit-logs-stack.yml index 18db2739186..1ee722b3a19 100644 --- a/build-infrastructure/audit-logs-stack.yml +++ b/build-infrastructure/audit-logs-stack.yml @@ -45,18 +45,18 @@ Resources: Type: AWS::S3::Bucket Properties: BucketName: !Sub 'audit-logs-bucket-${AWS::AccountId}' - - AuditLogsBucketPolicy: - DependsOn: AuditLogsBucket - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Sub 'audit-logs-bucket-${AWS::AccountId}' BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled + + AuditLogsBucketPolicy: + DependsOn: AuditLogsBucket + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Sub 'audit-logs-bucket-${AWS::AccountId}' PolicyDocument: Version: 2012-10-17 Statement: From 88847ca59da02a538754ff857c0b6bae5d104a37 Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Tue, 16 May 2023 10:36:07 -0700 Subject: [PATCH 5/9] Structured logging for docker image manager (#3696) * Structured logging for docker image manager * Add some reused fields to logger/field/constants.go * Add missing 'imageTag' field from err message --- agent/api/container/container.go | 16 ++++ agent/engine/docker_image_manager.go | 95 +++++++++++-------- agent/engine/docker_task_engine.go | 5 +- agent/engine/image/types.go | 24 ++++- agent/stats/container.go | 5 +- .../ecs-agent/logger/field/constants.go | 59 +++++++----- ecs-agent/logger/field/constants.go | 59 +++++++----- 7 files changed, 170 insertions(+), 93 deletions(-) diff --git a/agent/api/container/container.go b/agent/api/container/container.go index 686fbfbaa24..98d8f701c2f 100644 --- a/agent/api/container/container.go +++ b/agent/api/container/container.go @@ -26,6 +26,8 @@ import ( apierrors "github.com/aws/amazon-ecs-agent/agent/api/errors" resourcestatus "github.com/aws/amazon-ecs-agent/agent/taskresource/status" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" "github.com/aws/aws-sdk-go/aws" "github.com/cihub/seelog" @@ -509,6 +511,20 @@ func (c *Container) String() string { return ret } +func (c *Container) Fields() logger.Fields { + exitCode := "nil" + if c.GetKnownExitCode() != nil { + exitCode = strconv.Itoa(*c.GetKnownExitCode()) + } + return logger.Fields{ + field.ContainerName: c.Name, + field.ContainerImage: c.Image, + "containerKnownStatus": c.GetKnownStatus().String(), + "containerDesiredStatus": c.GetDesiredStatus().String(), + field.ContainerExitCode: exitCode, + } +} + // GetSteadyStateStatus returns the steady state status for the container. If // Container.steadyState is not initialized, the default steady state status // defined by `defaultContainerSteadyStateStatus` is returned. In awsvpc, the 'pause' diff --git a/agent/engine/docker_image_manager.go b/agent/engine/docker_image_manager.go index 56f8d61e1a3..31b981f791d 100644 --- a/agent/engine/docker_image_manager.go +++ b/agent/engine/docker_image_manager.go @@ -33,7 +33,6 @@ import ( "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" "github.com/aws/amazon-ecs-agent/agent/engine/image" - "github.com/cihub/seelog" ) const ( @@ -152,7 +151,9 @@ func (imageManager *dockerImageManager) RecordContainerReference(container *apic // Inspect image for obtaining Container's Image ID imageInspected, err := imageManager.client.InspectImage(container.Image) if err != nil { - seelog.Errorf("Error inspecting image %v: %v", container.Image, err) + fields := container.Fields() + fields[field.Error] = err + logger.Error("Error inspecting image", fields) return err } container.ImageID = imageInspected.ID @@ -177,7 +178,7 @@ func (imageManager *dockerImageManager) fetchRepoDigest(imageInspected *types.Im resultRepoDigest = repoDigestSplitList[1] return resultRepoDigest } else { - seelog.Warnf("ImageRepoDigest doesn't have the right format: %v", imageRepoDigest) + logger.Warn(fmt.Sprintf("ImageRepoDigest doesn't have the right format: %v", imageRepoDigest), container.Fields()) return "" } } @@ -272,7 +273,7 @@ func (imageManager *dockerImageManager) removeImageState(imageStateToBeRemoved * for i, imageState := range imageManager.imageStates { if imageState.Image.ImageID == imageStateToBeRemoved.Image.ImageID { // Image State found; hence remove it - seelog.Infof("Removing Image State: [%s] from Image Manager", imageState.String()) + logger.Debug("Image Manager: removing image state", imageState.Fields()) imageManager.imageStates = append(imageManager.imageStates[:i], imageManager.imageStates[i+1:]...) imageManager.removeImageStateData(imageState.Image.ImageID) return @@ -282,14 +283,14 @@ func (imageManager *dockerImageManager) removeImageState(imageStateToBeRemoved * func (imageManager *dockerImageManager) getCandidateImagesForDeletion() []*image.ImageState { if len(imageManager.imageStatesConsideredForDeletion) < 1 { - seelog.Debugf("Image Manager: Empty state!") + logger.Debug("Image Manager: Empty state!") // no image states present in image manager return nil } var imagesForDeletion []*image.ImageState for _, imageState := range imageManager.imageStatesConsideredForDeletion { if imageManager.isImageOldEnough(imageState) && imageState.HasNoAssociatedContainers() { - seelog.Infof("Candidate image for deletion: [%s]", imageState.String()) + logger.Debug("Candidate image for deletion", imageState.Fields()) imagesForDeletion = append(imagesForDeletion, imageState) } } @@ -346,7 +347,7 @@ func (imageManager *dockerImageManager) StartImageCleanupProcess(ctx context.Con // If the image pull behavior is prefer cached, don't clean up the image, // because the cached image is needed. if imageManager.imagePullBehavior == config.ImagePullPreferCachedBehavior { - seelog.Info("Pull behavior is set to always use cache. Disabling cleanup") + logger.Info("Pull behavior is set to always use cache. Disabling cleanup") return } // passing the cleanup interval as argument which would help during testing @@ -367,10 +368,10 @@ func (imageManager *dockerImageManager) performPeriodicImageCleanup(ctx context. } func (imageManager *dockerImageManager) removeUnusedImages(ctx context.Context) { - seelog.Debug("Attempting to obtain ImagePullDeleteLock for removing images") + logger.Debug("Attempting to obtain ImagePullDeleteLock for removing images") ImagePullDeleteLock.Lock() - seelog.Debug("Obtained ImagePullDeleteLock for removing images") - defer seelog.Debug("Released ImagePullDeleteLock after removing images") + logger.Debug("Obtained ImagePullDeleteLock for removing images") + defer logger.Debug("Released ImagePullDeleteLock after removing images") defer ImagePullDeleteLock.Unlock() imageManager.updateLock.Lock() @@ -383,7 +384,9 @@ func (imageManager *dockerImageManager) removeUnusedImages(ctx context.Context) err := imageManager.removeLeastRecentlyUsedImage(ctx) numECSImagesDeleted = i if err != nil { - seelog.Infof("End of eligible images for deletion: %v; Still have %d image states being managed", err, len(imageManager.getAllImageStates())) + logger.Info("End of eligible images for deletion", logger.Fields{ + "managedImagesRemaining": len(imageManager.getAllImageStates()), + }) break } } @@ -399,20 +402,20 @@ func (imageManager *dockerImageManager) removeUnusedImages(ctx context.Context) func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Context) { nonECSContainersIDs, err := imageManager.getNonECSContainerIDs(ctx) if err != nil { - seelog.Errorf("Error getting non-ECS container IDs: %v", err) + logger.Error(fmt.Sprintf("Error getting non-ECS container IDs: %v", err)) } var nonECSContainerRemoveAvailableIDs []string for _, id := range nonECSContainersIDs { response, icErr := imageManager.client.InspectContainer(ctx, id, dockerclient.InspectContainerTimeout) if icErr != nil { - seelog.Errorf("Error inspecting non-ECS container id: %s - %v", id, icErr) + logger.Error(fmt.Sprintf("Error inspecting non-ECS container id: %s - %v", id, icErr)) continue } - seelog.Debugf("Inspecting Non-ECS Container ID [%s] for removal, Finished [%s] Status [%s]", id, response.State.FinishedAt, response.State.Status) + logger.Debug(fmt.Sprintf("Inspecting Non-ECS Container ID [%s] for removal, Finished [%s] Status [%s]", id, response.State.FinishedAt, response.State.Status)) finishedTime, err := time.Parse(time.RFC3339Nano, response.State.FinishedAt) if err != nil { - seelog.Errorf("Error parsing time string for container. id: %s, time: %s err: %s", id, response.State.FinishedAt, err) + logger.Error(fmt.Sprintf("Error parsing time string for container. id: %s, time: %s err: %s", id, response.State.FinishedAt, err)) continue } @@ -428,13 +431,17 @@ func (imageManager *dockerImageManager) removeNonECSContainers(ctx context.Conte if numNonECSContainerDeleted == imageManager.numNonECSContainersToDelete { break } - seelog.Debugf("Removing non-ECS Container ID %s", id) + logger.Debug("Removing non-ECS container", logger.Fields{ + "runtimeID": id, + }) err := imageManager.client.RemoveContainer(ctx, id, dockerclient.RemoveContainerTimeout) if err == nil { - seelog.Infof("Removed Container ID: %s", id) + logger.Info("Removed non-ECS container", logger.Fields{ + "runtimeID": id, + }) numNonECSContainerDeleted++ } else { - seelog.Errorf("Error Removing Container ID %s - %s", id, err) + logger.Error(fmt.Sprintf("Error Removing Container ID %s - %s", id, err)) continue } } @@ -480,24 +487,32 @@ func (imageManager *dockerImageManager) removeNonECSImages(ctx context.Context, if !imageManager.nonECSImageOldEnough(image) { continue } + fields := logger.Fields{ + field.ImageID: image.ImageID, + field.ImageSizeBytes: image.Size, + "repoTags": image.RepoTags, + } if len(image.RepoTags) > 1 { - seelog.Debugf("Non-ECS image has more than one tag Image: %s (Tags: %s)", image.ImageID, image.RepoTags) + logger.Debug("Non-ECS image has more than one tag", fields) for _, tag := range image.RepoTags { err := imageManager.client.RemoveImage(ctx, tag, dockerclient.RemoveImageTimeout) if err != nil { - seelog.Errorf("Error removing RepoTag (ImageID: %s, Tag: %s) %v", image.ImageID, tag, err) + logger.Error("Error removing non-ECS RepoTag", fields, logger.Fields{ + field.Error: err, + "imageTag": tag, + }) } else { - seelog.Infof("Image Tag Removed: %s (ImageID: %s)", tag, image.ImageID) + logger.Info("Non-ECS image tag removed", fields, logger.Fields{"imageTag": tag}) numImagesAlreadyDeleted++ } } } else { - seelog.Debugf("Removing non-ECS Image: %s (Tags: %s)", image.ImageID, image.RepoTags) + logger.Debug("Removing non-ECS image", fields) err := imageManager.client.RemoveImage(ctx, image.ImageID, dockerclient.RemoveImageTimeout) if err != nil { - seelog.Errorf("Error removing Image %s (Tags: %s) - %v", image.ImageID, image.RepoTags, err) + logger.Error("Error removing non-ECS image", fields, logger.Fields{field.Error: err}) } else { - seelog.Infof("Image removed: %s (Tags: %s)", image.ImageID, image.RepoTags) + logger.Info("Non-ECS image removed", fields) numImagesAlreadyDeleted++ } } @@ -512,13 +527,14 @@ func (imageManager *dockerImageManager) getNonECSImages(ctx context.Context) []I for _, imageID := range r.ImageIDs { resp, err := imageManager.client.InspectImage(imageID) if err != nil { - seelog.Errorf("Error inspecting non-ECS image: (ImageID: %s), %s", imageID, err) + logger.Error("Error inspecting non-ECS image", logger.Fields{field.ImageID: imageID, field.Error: err}) continue } createTime := time.Time{} createTime, err = time.Parse(time.RFC3339, resp.Created) if err != nil { - seelog.Warnf("Error parse the inspected non-ECS image created time (ImageID: %s), %v", imageID, err) + logger.Warn("Error parsing the inspected non-ECS image created time.", + logger.Fields{field.ImageID: imageID, field.Error: err}) } allImages = append(allImages, ImageWithSizeID{ @@ -588,13 +604,13 @@ func exclude(allList []string, exclusionList []string) []string { func (imageManager *dockerImageManager) imagesConsiderForDeletion(allImageStates []*image.ImageState) map[string]*image.ImageState { var imagesConsiderForDeletionMap = make(map[string]*image.ImageState) - seelog.Info("Begin building map of eligible unused images for deletion") + logger.Debug("Begin building map of eligible unused images for deletion") for _, imageState := range allImageStates { if imageManager.isExcludedFromCleanup(imageState) { //imageState that we want to keep - seelog.Debugf("Image excluded from deletion: [%s]", imageState.String()) + logger.Debug("Image excluded from deletion", imageState.Fields()) } else { - seelog.Debugf("Image going to be considered for deletion: [%s]", imageState.String()) + logger.Debug("Image going to be considered for deletion", imageState.Fields()) imagesConsiderForDeletionMap[imageState.Image.ImageID] = imageState } } @@ -617,6 +633,7 @@ func (imageManager *dockerImageManager) removeLeastRecentlyUsedImage(ctx context if leastRecentlyUsedImage == nil { return fmt.Errorf("No more eligible images for deletion") } + logger.Info("Image ready for deletion", leastRecentlyUsedImage.Fields()) imageManager.removeImage(ctx, leastRecentlyUsedImage) return nil } @@ -624,10 +641,10 @@ func (imageManager *dockerImageManager) removeLeastRecentlyUsedImage(ctx context func (imageManager *dockerImageManager) getUnusedImageForDeletion() *image.ImageState { candidateImageStatesForDeletion := imageManager.getCandidateImagesForDeletion() if len(candidateImageStatesForDeletion) < 1 { - seelog.Infof("No eligible images for deletion for this cleanup cycle") + logger.Debug("No eligible images for deletion for this cleanup cycle") return nil } - seelog.Infof("Found %d eligible images for deletion", len(candidateImageStatesForDeletion)) + logger.Debug(fmt.Sprintf("Found %d eligible images for deletion", len(candidateImageStatesForDeletion))) return imageManager.getLeastRecentlyUsedImage(candidateImageStatesForDeletion) } @@ -648,24 +665,28 @@ func (imageManager *dockerImageManager) removeImage(ctx context.Context, leastRe func (imageManager *dockerImageManager) deleteImage(ctx context.Context, imageID string, imageState *image.ImageState) { if imageID == "" { - seelog.Errorf("Image ID to be deleted is null") + var fields logger.Fields + if imageState != nil { + fields = imageState.Fields() + } + logger.Error("Image ID to be deleted is null", fields) return } - seelog.Infof("Removing Image: %s", imageID) + logger.Debug(fmt.Sprintf("Removing Image: %s", imageID), imageState.Fields()) err := imageManager.client.RemoveImage(ctx, imageID, dockerclient.RemoveImageTimeout) if err != nil { if strings.Contains(strings.ToLower(err.Error()), imageNotFoundForDeletionError) { - seelog.Errorf("Image already removed from the instance: %v", err) + logger.Error(fmt.Sprintf("Image already removed from the instance: %v", imageID), imageState.Fields()) } else { - seelog.Errorf("Error removing Image %v - %v", imageID, err) + logger.Error(fmt.Sprintf("Error removing Image %v", imageID), imageState.Fields(), logger.Fields{field.Error: err}) delete(imageManager.imageStatesConsideredForDeletion, imageState.Image.ImageID) return } } - seelog.Infof("Image removed: %v", imageID) + logger.Info(fmt.Sprintf("Image removed: %v", imageID), imageState.Fields()) imageState.RemoveImageName(imageID) if len(imageState.Image.Names) == 0 { - seelog.Infof("Cleaning up all tracking information for image %s as it has zero references", imageID) + logger.Info(fmt.Sprintf("Cleaning up all tracking information for image %s as it has zero references", imageID), imageState.Fields()) delete(imageManager.imageStatesConsideredForDeletion, imageState.Image.ImageID) imageManager.removeImageState(imageState) imageManager.state.RemoveImageState(imageState) diff --git a/agent/engine/docker_task_engine.go b/agent/engine/docker_task_engine.go index 82e759facbf..a9349650435 100644 --- a/agent/engine/docker_task_engine.go +++ b/agent/engine/docker_task_engine.go @@ -2090,9 +2090,8 @@ func (engine *DockerTaskEngine) stopDockerContainer(dockerID, containerName stri } func (engine *DockerTaskEngine) removeContainer(task *apitask.Task, container *apicontainer.Container) error { - logger.Info("Removing container", logger.Fields{ - field.TaskID: task.GetID(), - field.Container: container.Name, + logger.Info("Removing container", container.Fields(), logger.Fields{ + field.TaskID: task.GetID(), }) dockerID, err := engine.getDockerID(task, container) if err != nil { diff --git a/agent/engine/image/types.go b/agent/engine/image/types.go index c87d17e9f7f..46be2dd1721 100644 --- a/agent/engine/image/types.go +++ b/agent/engine/image/types.go @@ -188,5 +188,27 @@ func (imageState *ImageState) String() string { image = imageState.Image.String() } return fmt.Sprintf("Image: [%s] referenced by %d containers; PulledAt: %s; LastUsedAt: %s; PullSucceeded: %t", - image, len(imageState.Containers), imageState.PulledAt.String(), imageState.LastUsedAt.String(), imageState.PullSucceeded) + image, len(imageState.Containers), imageState.PulledAt.UTC().Format(time.RFC3339), + imageState.LastUsedAt.UTC().Format(time.RFC3339), imageState.PullSucceeded) +} + +func (imageState *ImageState) Fields() logger.Fields { + imageID := "" + names := []string{} + size := int64(-1) + if imageState.Image != nil { + imageID = imageState.Image.ImageID + names = imageState.Image.Names + size = imageState.Image.Size + } + + return logger.Fields{ + field.ImageID: imageID, + field.ImageNames: names, + field.ImageSizeBytes: size, + "referencedBy": len(imageState.Containers), + field.ImagePulledAt: imageState.PulledAt.UTC().Format(time.RFC3339), + field.ImageLastUsedAt: imageState.LastUsedAt.UTC().Format(time.RFC3339), + field.ImagePullSucceeded: imageState.PullSucceeded, + } } diff --git a/agent/stats/container.go b/agent/stats/container.go index 722df196797..e1fa6ba0653 100644 --- a/agent/stats/container.go +++ b/agent/stats/container.go @@ -24,6 +24,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" "github.com/aws/amazon-ecs-agent/agent/stats/resolver" "github.com/aws/amazon-ecs-agent/agent/utils/retry" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger" "github.com/cihub/seelog" ) @@ -74,7 +75,7 @@ func (container *StatsContainer) collect() { err := container.processStatsStream() select { case <-container.ctx.Done(): - seelog.Infof("Container [%s]: Stopping stats collection", dockerID) + logger.Info("Stopping container stats collection", logger.Fields{"runtimeID": dockerID}) return default: if err != nil { @@ -97,7 +98,7 @@ func (container *StatsContainer) collect() { container.StopStatsCollection() return } else if terminal { - seelog.Infof("Container [%s]: container is terminal, stopping stats collection", dockerID) + logger.Info("Container is terminal, stopping stats collection", logger.Fields{"runtimeID": dockerID}) container.StopStatsCollection() return } diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/field/constants.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/field/constants.go index fbfd713ffe9..8b154e1dbec 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/field/constants.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/field/constants.go @@ -14,29 +14,38 @@ package field const ( - TaskID = "task" - TaskARN = "taskARN" - Container = "container" - DockerId = "dockerId" - ManagedAgent = "managedAgent" - KnownStatus = "knownStatus" - KnownSent = "knownSent" - DesiredStatus = "desiredStatus" - SentStatus = "sentStatus" - FailedStatus = "failedStatus" - Sequence = "seqnum" - Reason = "reason" - Status = "status" - RuntimeID = "runtimeID" - Elapsed = "elapsed" - Resource = "resource" - Error = "error" - Event = "event" - Image = "image" - Volume = "volume" - Time = "time" - NetworkMode = "networkMode" - Cluster = "cluster" - ServiceName = "ServiceName" - TaskProtection = "TaskProtection" + TaskID = "task" + TaskARN = "taskARN" + Container = "container" + DockerId = "dockerId" + ManagedAgent = "managedAgent" + KnownStatus = "knownStatus" + KnownSent = "knownSent" + DesiredStatus = "desiredStatus" + SentStatus = "sentStatus" + FailedStatus = "failedStatus" + Sequence = "seqnum" + Reason = "reason" + Status = "status" + RuntimeID = "runtimeID" + Elapsed = "elapsed" + Resource = "resource" + Error = "error" + Event = "event" + Image = "image" + Volume = "volume" + Time = "time" + NetworkMode = "networkMode" + Cluster = "cluster" + ServiceName = "ServiceName" + TaskProtection = "TaskProtection" + ImageID = "imageID" + ImageNames = "imageNames" + ImageSizeBytes = "imageSizeBytes" + ImagePulledAt = "imagePulledAt" + ImageLastUsedAt = "imageLastUsedAt" + ImagePullSucceeded = "imagePullSucceeded" + ContainerName = "containerName" + ContainerImage = "containerImage" + ContainerExitCode = "containerExitCode" ) diff --git a/ecs-agent/logger/field/constants.go b/ecs-agent/logger/field/constants.go index fbfd713ffe9..8b154e1dbec 100644 --- a/ecs-agent/logger/field/constants.go +++ b/ecs-agent/logger/field/constants.go @@ -14,29 +14,38 @@ package field const ( - TaskID = "task" - TaskARN = "taskARN" - Container = "container" - DockerId = "dockerId" - ManagedAgent = "managedAgent" - KnownStatus = "knownStatus" - KnownSent = "knownSent" - DesiredStatus = "desiredStatus" - SentStatus = "sentStatus" - FailedStatus = "failedStatus" - Sequence = "seqnum" - Reason = "reason" - Status = "status" - RuntimeID = "runtimeID" - Elapsed = "elapsed" - Resource = "resource" - Error = "error" - Event = "event" - Image = "image" - Volume = "volume" - Time = "time" - NetworkMode = "networkMode" - Cluster = "cluster" - ServiceName = "ServiceName" - TaskProtection = "TaskProtection" + TaskID = "task" + TaskARN = "taskARN" + Container = "container" + DockerId = "dockerId" + ManagedAgent = "managedAgent" + KnownStatus = "knownStatus" + KnownSent = "knownSent" + DesiredStatus = "desiredStatus" + SentStatus = "sentStatus" + FailedStatus = "failedStatus" + Sequence = "seqnum" + Reason = "reason" + Status = "status" + RuntimeID = "runtimeID" + Elapsed = "elapsed" + Resource = "resource" + Error = "error" + Event = "event" + Image = "image" + Volume = "volume" + Time = "time" + NetworkMode = "networkMode" + Cluster = "cluster" + ServiceName = "ServiceName" + TaskProtection = "TaskProtection" + ImageID = "imageID" + ImageNames = "imageNames" + ImageSizeBytes = "imageSizeBytes" + ImagePulledAt = "imagePulledAt" + ImageLastUsedAt = "imageLastUsedAt" + ImagePullSucceeded = "imagePullSucceeded" + ContainerName = "containerName" + ContainerImage = "containerImage" + ContainerExitCode = "containerExitCode" ) From 548848bb6fb1310e2c5e7cb9b09de0aaa10de16a Mon Sep 17 00:00:00 2001 From: Donovan <45215226+fincd-aws@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:00:50 -0700 Subject: [PATCH 6/9] Add ECS_GMSA_SUPPORTED env var to README ECS_GMSA_SUPPORTED looks to default to false for both Linux [agent/config/parse_linux.go](https://github.com/aws/amazon-ecs-agent/blob/b32ab0754c9c1b82e8c6d67dc095b6ac1e7cb49e/agent/config/parse_linux.go#L28) and Windows [agent/config/parse_windows.go](https://github.com/aws/amazon-ecs-agent/blob/b32ab0754c9c1b82e8c6d67dc095b6ac1e7cb49e/agent/config/parse_windows.go#L38). --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4c2c1da70ab..1b521cbdaaf 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,7 @@ additional details on each available environment variable. | `ECS_ENABLE_GPU_SUPPORT` | `true` | Whether you use container instances with GPU support. This parameter is specified for the agent. You must also configure your task definitions for GPU. For more information | `false` | `Not applicable` | | `HTTP_PROXY` | `10.0.0.131:3128` | The hostname (or IP address) and port number of an HTTP proxy to use for the Amazon ECS agent to connect to the internet. For example, this proxy will be used if your container instances do not have external network access through an Amazon VPC internet gateway or NAT gateway or instance. If this variable is set, you must also set the NO_PROXY variable to filter Amazon EC2 instance metadata and Docker daemon traffic from the proxy. | `null` | `null` | | `NO_PROXY` | | The HTTP traffic that should not be forwarded to the specified HTTP_PROXY. You must specify 169.254.169.254,/var/run/docker.sock to filter Amazon EC2 instance metadata and Docker daemon traffic from the proxy. | `null` | `null` | +| `ECS_GMSA_SUPPORTED` | `true` | Whether you use gMSA authentication to Active Directory in tasks. Each task must specify the location of a credential specification file in the `dockerSecurityOpts` parameter of a container definition. On Linux, this requires the [credentials-fetcher daemon](https://github.com/aws/credentials-fetcher). | `false` | `false` | | `CREDENTIALS_FETCHER_HOST` | `unix:///var/credentials-fetcher/socket/credentials_fetcher.sock` | Used to create a connection to the [credentials-fetcher daemon](https://github.com/aws/credentials-fetcher); to support gMSA on Linux. The default is fine for most users, only needs to be modified if user is configuring a custom credentials-fetcher socket path, ie, [CF_UNIX_DOMAIN_SOCKET_DIR](https://github.com/aws/credentials-fetcher#default-environment-variables). | `unix:///var/credentials-fetcher/socket/credentials_fetcher.sock` | Not Applicable | | `CREDENTIALS_FETCHER_SECRET_NAME_FOR_DOMAINLESS_GMSA` | `secretmanager-secretname` | Used to support scaling option for gMSA on Linux [credentials-fetcher daemon](https://github.com/aws/credentials-fetcher). If user is configuring gMSA on a non-domain joined instance, they need to create an Active Directory user with access to retrieve principals for the gMSA account and store it in secrets manager | `secretmanager-secretname` | Not Applicable | | `ECS_DYNAMIC_HOST_PORT_RANGE` | `100-200` | This specifies the dynamic host port range that the agent uses to assign host ports from, for container ports mapping. If there are no available ports in the range for containers, including customer containers and Service Connect Agent containers (if Service Connect is enabled), service deployments would fail. | Defined by `/proc/sys/net/ipv4/ip_local_port_range` | `49152-65535` | From a5bd61552818158f15b82c12df2e48f2709b8eb1 Mon Sep 17 00:00:00 2001 From: Dane H Lim Date: Tue, 2 May 2023 17:01:05 -0700 Subject: [PATCH 7/9] Common task manifest/stop verification ACK in agent/ --- agent/acs/handler/acs_handler.go | 5 +++- agent/acs/handler/task_manifest_common.go | 23 ++++++++++++++++ agent/acs/handler/task_manifest_handler.go | 27 ++++++------------- .../acs/handler/task_manifest_handler_test.go | 22 ++++++++++----- .../acs/session/task_manifest_common.go | 9 +++++++ 5 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 agent/acs/handler/task_manifest_common.go create mode 100644 agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/session/task_manifest_common.go diff --git a/agent/acs/handler/acs_handler.go b/agent/acs/handler/acs_handler.go index 1b9515fa6fd..47fda2a5a78 100644 --- a/agent/acs/handler/acs_handler.go +++ b/agent/acs/handler/acs_handler.go @@ -315,9 +315,12 @@ func (acsSession *session) startACSSession(client wsclient.ClientServer) error { client.AddRequestHandler(instanceENIAttachHandler.handlerFunc()) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} + // Add TaskManifestHandler taskManifestHandler := newTaskManifestHandler(acsSession.ctx, cfg.Cluster, acsSession.containerInstanceARN, - client, acsSession.dataClient, acsSession.taskEngine, acsSession.latestSeqNumTaskManifest) + client, acsSession.dataClient, acsSession.taskEngine, acsSession.latestSeqNumTaskManifest, + manifestMessageIDAccessor) defer taskManifestHandler.clearAcks() taskManifestHandler.start() diff --git a/agent/acs/handler/task_manifest_common.go b/agent/acs/handler/task_manifest_common.go new file mode 100644 index 00000000000..da63c79a9bf --- /dev/null +++ b/agent/acs/handler/task_manifest_common.go @@ -0,0 +1,23 @@ +package handler + +import ( + "sync" +) + +type manifestMessageIDAccessor struct { + messageID string + lock sync.RWMutex +} + +func (mmiAccessor *manifestMessageIDAccessor) GetMessageID() string { + mmiAccessor.lock.RLock() + defer mmiAccessor.lock.RUnlock() + return mmiAccessor.messageID +} + +func (mmiAccessor *manifestMessageIDAccessor) SetMessageID(messageId string) error { + mmiAccessor.lock.Lock() + defer mmiAccessor.lock.Unlock() + mmiAccessor.messageID = messageId + return nil +} diff --git a/agent/acs/handler/task_manifest_handler.go b/agent/acs/handler/task_manifest_handler.go index e2be7bd376f..2e6209f4705 100644 --- a/agent/acs/handler/task_manifest_handler.go +++ b/agent/acs/handler/task_manifest_handler.go @@ -15,7 +15,6 @@ package handler import ( "context" "strconv" - "sync" apitask "github.com/aws/amazon-ecs-agent/agent/api/task" apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status" @@ -23,6 +22,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/engine" "github.com/aws/amazon-ecs-agent/agent/wsclient" "github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs" + acssession "github.com/aws/amazon-ecs-agent/ecs-agent/acs/session" "github.com/aws/aws-sdk-go/aws" "github.com/cihub/seelog" ) @@ -41,14 +41,14 @@ type taskManifestHandler struct { containerInstanceArn string acsClient wsclient.ClientServer latestSeqNumberTaskManifest *int64 - messageId string - lock sync.RWMutex + manifestMessageIDAccessor acssession.ManifestMessageIDAccessor } // newTaskManifestHandler returns an instance of the taskManifestHandler struct func newTaskManifestHandler(ctx context.Context, cluster string, containerInstanceArn string, acsClient wsclient.ClientServer, - dataClient data.Client, taskEngine engine.TaskEngine, latestSeqNumberTaskManifest *int64) taskManifestHandler { + dataClient data.Client, taskEngine engine.TaskEngine, latestSeqNumberTaskManifest *int64, + manifestMessageIDAccessor acssession.ManifestMessageIDAccessor) taskManifestHandler { // Create a cancelable context from the parent context derivedContext, cancel := context.WithCancel(ctx) @@ -65,6 +65,7 @@ func newTaskManifestHandler(ctx context.Context, taskEngine: taskEngine, dataClient: dataClient, latestSeqNumberTaskManifest: latestSeqNumberTaskManifest, + manifestMessageIDAccessor: manifestMessageIDAccessor, } } @@ -93,18 +94,6 @@ func (taskManifestHandler *taskManifestHandler) start() { } -func (taskManifestHandler *taskManifestHandler) getMessageId() string { - taskManifestHandler.lock.RLock() - defer taskManifestHandler.lock.RUnlock() - return taskManifestHandler.messageId -} - -func (taskManifestHandler *taskManifestHandler) setMessageId(messageId string) { - taskManifestHandler.lock.Lock() - defer taskManifestHandler.lock.Unlock() - taskManifestHandler.messageId = messageId -} - func (taskManifestHandler *taskManifestHandler) sendTaskManifestMessageAck() { for { select { @@ -243,10 +232,10 @@ func compareTasks(receivedTaskList []*ecsacs.TaskIdentifier, runningTaskList []* func (taskManifestHandler *taskManifestHandler) handleSingleMessageVerificationAck( message *ecsacs.TaskStopVerificationAck) error { // Ensure that we have received a corresponding task manifest message before - taskManifestMessageId := taskManifestHandler.getMessageId() + taskManifestMessageId := taskManifestHandler.manifestMessageIDAccessor.GetMessageID() if taskManifestMessageId != "" && *message.MessageId == taskManifestMessageId { // Reset the message id so that the message with same message id is not processed twice - taskManifestHandler.setMessageId("") + taskManifestHandler.manifestMessageIDAccessor.SetMessageID("") for _, taskToKill := range message.StopTasks { if *taskToKill.DesiredStatus == apitaskstatus.TaskStoppedString { task, isPresent := taskManifestHandler.taskEngine.GetTaskByArn(*taskToKill.TaskArn) @@ -286,7 +275,7 @@ func (taskManifestHandler *taskManifestHandler) handleTaskManifestSingleMessage( tasksToKill := compareTasks(taskListManifestHandler, runningTasksOnInstance, clusterARN) // Update messageId so that it can be compared to the messageId in TaskStopVerificationAck message - taskManifestHandler.setMessageId(*message.MessageId) + taskManifestHandler.manifestMessageIDAccessor.SetMessageID(*message.MessageId) // Throw the task manifest ack and task verification message in async so that it does not block the current // thread. diff --git a/agent/acs/handler/task_manifest_handler_test.go b/agent/acs/handler/task_manifest_handler_test.go index 66641a13251..697bba3600b 100644 --- a/agent/acs/handler/task_manifest_handler_test.go +++ b/agent/acs/handler/task_manifest_handler_test.go @@ -53,9 +53,10 @@ func TestManifestHandlerKillAllTasks(t *testing.T) { mockWSClient := mock_wsclient.NewMockClientServer(ctrl) dataClient := newTestDataClient(t) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} newTaskManifest := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - dataClient, taskEngine, aws.Int64(11)) + dataClient, taskEngine, aws.Int64(11), manifestMessageIDAccessor) ackRequested := &ecsacs.AckRequest{ Cluster: aws.String(cluster), @@ -147,9 +148,10 @@ func TestManifestHandlerKillFewTasks(t *testing.T) { mockWSClient := mock_wsclient.NewMockClientServer(ctrl) dataClient := newTestDataClient(t) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} newTaskManifest := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - dataClient, taskEngine, aws.Int64(11)) + dataClient, taskEngine, aws.Int64(11), manifestMessageIDAccessor) ackRequested := &ecsacs.AckRequest{ Cluster: aws.String(cluster), @@ -250,9 +252,10 @@ func TestManifestHandlerKillNoTasks(t *testing.T) { mockWSClient := mock_wsclient.NewMockClientServer(ctrl) dataClient := newTestDataClient(t) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} newTaskManifest := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - dataClient, taskEngine, aws.Int64(11)) + dataClient, taskEngine, aws.Int64(11), manifestMessageIDAccessor) ackRequested := &ecsacs.AckRequest{ Cluster: aws.String(cluster), @@ -339,9 +342,10 @@ func TestManifestHandlerDifferentTaskLists(t *testing.T) { mockWSClient := mock_wsclient.NewMockClientServer(ctrl) dataClient := newTestDataClient(t) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} newTaskManifest := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - dataClient, taskEngine, aws.Int64(11)) + dataClient, taskEngine, aws.Int64(11), manifestMessageIDAccessor) ackRequested := &ecsacs.AckRequest{ Cluster: aws.String(cluster), @@ -460,8 +464,10 @@ func TestManifestHandlerSequenceNumbers(t *testing.T) { ctx := context.TODO() mockWSClient := mock_wsclient.NewMockClientServer(ctrl) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} + newTaskManifest := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - data.NewNoopClient(), taskEngine, aws.Int64(tc.inputSequenceNumber)) + data.NewNoopClient(), taskEngine, aws.Int64(tc.inputSequenceNumber), manifestMessageIDAccessor) taskList := []*task.Task{ {Arn: "arn2", DesiredStatusUnsafe: apitaskstatus.TaskRunning}, @@ -554,8 +560,9 @@ func TestTaskManifestHandlerSendPendingTaskManifestMessageAck(t *testing.T) { taskEngine := mock_engine.NewMockTaskEngine(ctrl) mockWSClient := mock_wsclient.NewMockClientServer(ctrl) mockWSClient.EXPECT().MakeRequest(gomock.Any()).Return(nil).Times(1) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} handler := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - data.NewNoopClient(), taskEngine, aws.Int64(testSeqNum)) + data.NewNoopClient(), taskEngine, aws.Int64(testSeqNum), manifestMessageIDAccessor) wg := sync.WaitGroup{} wg.Add(2) @@ -590,8 +597,9 @@ func TestTaskManifestHandlerHandlePendingTaskStopVerificationAck(t *testing.T) { ctx := context.TODO() taskEngine := mock_engine.NewMockTaskEngine(ctrl) mockWSClient := mock_wsclient.NewMockClientServer(ctrl) + manifestMessageIDAccessor := &manifestMessageIDAccessor{} handler := newTaskManifestHandler(ctx, cluster, containerInstanceArn, mockWSClient, - data.NewNoopClient(), taskEngine, aws.Int64(testSeqNum)) + data.NewNoopClient(), taskEngine, aws.Int64(testSeqNum), manifestMessageIDAccessor) wg := sync.WaitGroup{} wg.Add(2) diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/session/task_manifest_common.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/session/task_manifest_common.go new file mode 100644 index 00000000000..896583fdb82 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/acs/session/task_manifest_common.go @@ -0,0 +1,9 @@ +package session + +// ManifestMessageIDAccessor stores the latest message ID that corresponds to the +// most recently processed task manifest message and is used to determine the +// validity of a task stop verification ack message. +type ManifestMessageIDAccessor interface { + GetMessageID() string + SetMessageID(messageID string) error +} From 70992c92be53b08a7a50b82e0ab3ef2e3e1434ee Mon Sep 17 00:00:00 2001 From: Amogh Rathore Date: Wed, 17 May 2023 13:56:38 -0700 Subject: [PATCH 8/9] Move v1 and v2 credentials endpoint handlers to ecs-agent module (#3698) --- agent/config/config.go | 9 +- agent/handlers/task_server_setup.go | 9 +- agent/handlers/task_server_setup_test.go | 13 +- agent/logger/audit/audit_log_test.go | 19 +- agent/logger/audit/entry_types.go | 21 +- agent/utils/utils.go | 25 +- agent/utils/utils_test.go | 47 --- .../ecs-agent/logger/audit/audit_log.go | 23 +- .../tmds}/handlers/v1/credentials_handler.go | 48 ++- .../tmds}/handlers/v2/credentials_handler.go | 2 +- .../amazon-ecs-agent/ecs-agent/utils/utils.go | 37 +++ agent/vendor/modules.txt | 3 + ecs-agent/logger/audit/audit_log.go | 23 +- .../tmds/handlers/credentials_handler_test.go | 312 ++++++++++++++++++ .../tmds/handlers/v1/credentials_handler.go | 181 ++++++++++ .../tmds/handlers/v2/credentials_handler.go | 54 +++ ecs-agent/utils/utils.go | 37 +++ ecs-agent/utils/utils_test.go | 61 ++++ 18 files changed, 802 insertions(+), 122 deletions(-) rename agent/{ => vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds}/handlers/v1/credentials_handler.go (81%) rename agent/{ => vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds}/handlers/v2/credentials_handler.go (97%) create mode 100644 agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/utils/utils.go create mode 100644 ecs-agent/tmds/handlers/credentials_handler_test.go create mode 100644 ecs-agent/tmds/handlers/v1/credentials_handler.go create mode 100644 ecs-agent/tmds/handlers/v2/credentials_handler.go create mode 100644 ecs-agent/utils/utils.go create mode 100644 ecs-agent/utils/utils_test.go diff --git a/agent/config/config.go b/agent/config/config.go index bdd4381dec7..7230a5c3f48 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -27,6 +27,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/dockerclient" "github.com/aws/amazon-ecs-agent/agent/ec2" "github.com/aws/amazon-ecs-agent/agent/utils" + commonutils "github.com/aws/amazon-ecs-agent/ecs-agent/utils" "github.com/cihub/seelog" ) @@ -204,7 +205,7 @@ func (cfg *Config) Merge(rhs Config) *Config { leftField.Set(reflect.ValueOf(right.Field(i).Interface())) } default: - if utils.ZeroOrNil(leftField.Interface()) { + if commonutils.ZeroOrNil(leftField.Interface()) { leftField.Set(reflect.ValueOf(right.Field(i).Interface())) } } @@ -395,7 +396,7 @@ func (cfg *Config) checkMissingAndDepreciated() error { fatalFields := []string{} for i := 0; i < cfgElem.NumField(); i++ { cfgField := cfgElem.Field(i) - if utils.ZeroOrNil(cfgField.Interface()) { + if commonutils.ZeroOrNil(cfgField.Interface()) { missingTag := cfgStructField.Field(i).Tag.Get("missing") if len(missingTag) == 0 { continue @@ -429,7 +430,7 @@ func (cfg *Config) complete() bool { cfgElem := reflect.ValueOf(cfg).Elem() for i := 0; i < cfgElem.NumField(); i++ { - if utils.ZeroOrNil(cfgElem.Field(i).Interface()) { + if commonutils.ZeroOrNil(cfgElem.Field(i).Interface()) { return false } } @@ -464,7 +465,7 @@ func fileConfig() (Config, error) { } // Handle any deprecated keys correctly here - if utils.ZeroOrNil(cfg.Cluster) && !utils.ZeroOrNil(cfg.ClusterArn) { + if commonutils.ZeroOrNil(cfg.Cluster) && !commonutils.ZeroOrNil(cfg.ClusterArn) { cfg.Cluster = cfg.ClusterArn } return cfg, nil diff --git a/agent/handlers/task_server_setup.go b/agent/handlers/task_server_setup.go index c2f46fd18ba..fe5e74c35a0 100644 --- a/agent/handlers/task_server_setup.go +++ b/agent/handlers/task_server_setup.go @@ -22,7 +22,6 @@ import ( "github.com/aws/amazon-ecs-agent/agent/config" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" agentAPITaskProtectionV1 "github.com/aws/amazon-ecs-agent/agent/handlers/agentapi/taskprotection/v1/handlers" - v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" v4 "github.com/aws/amazon-ecs-agent/agent/handlers/v4" @@ -32,6 +31,8 @@ import ( "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds" + tmdsv1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" + tmdsv2 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2" "github.com/cihub/seelog" "github.com/gorilla/mux" ) @@ -67,8 +68,8 @@ func taskServerSetup(credentialsManager credentials.Manager, // to permanently redirect(301) to "/v3/metadata/task" handler muxRouter.SkipClean(false) - muxRouter.HandleFunc(v1.CredentialsPath, - v1.CredentialsHandler(credentialsManager, auditLogger)) + muxRouter.HandleFunc(tmdsv1.CredentialsPath, + tmdsv1.CredentialsHandler(credentialsManager, auditLogger)) v2HandlersSetup(muxRouter, state, ecsClient, statsEngine, cluster, credentialsManager, auditLogger, availabilityZone, containerInstanceArn) @@ -97,7 +98,7 @@ func v2HandlersSetup(muxRouter *mux.Router, auditLogger auditinterface.AuditLogger, availabilityZone string, containerInstanceArn string) { - muxRouter.HandleFunc(v2.CredentialsPath, v2.CredentialsHandler(credentialsManager, auditLogger)) + muxRouter.HandleFunc(tmdsv2.CredentialsPath, tmdsv2.CredentialsHandler(credentialsManager, auditLogger)) muxRouter.HandleFunc(v2.ContainerMetadataPath, v2.TaskContainerMetadataHandler(state, ecsClient, cluster, availabilityZone, containerInstanceArn, false)) muxRouter.HandleFunc(v2.TaskMetadataPath, v2.TaskContainerMetadataHandler(state, ecsClient, cluster, availabilityZone, containerInstanceArn, false)) muxRouter.HandleFunc(v2.TaskWithTagsMetadataPath, v2.TaskContainerMetadataHandler(state, ecsClient, cluster, availabilityZone, containerInstanceArn, true)) diff --git a/agent/handlers/task_server_setup_test.go b/agent/handlers/task_server_setup_test.go index 18692462333..2e8807617db 100644 --- a/agent/handlers/task_server_setup_test.go +++ b/agent/handlers/task_server_setup_test.go @@ -51,6 +51,7 @@ import ( mock_credentials "github.com/aws/amazon-ecs-agent/ecs-agent/credentials/mocks" mock_audit "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + tmdsv1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" "github.com/aws/aws-sdk-go/aws" "github.com/docker/docker/api/types" "github.com/golang/mock/gomock" @@ -535,7 +536,7 @@ func TestInvalidPath(t *testing.T) { // query parameters are not specified for the credentials endpoint. func TestCredentialsV1RequestWithNoArguments(t *testing.T) { msg := &utils.ErrorMessage{ - Code: v1.ErrNoIDInRequest, + Code: tmdsv1.ErrNoIDInRequest, Message: "CredentialsV1Request: No ID in the request", HTTPErrorCode: http.StatusBadRequest, } @@ -546,7 +547,7 @@ func TestCredentialsV1RequestWithNoArguments(t *testing.T) { // query parameters are not specified for the credentials endpoint. func TestCredentialsV2RequestWithNoArguments(t *testing.T) { msg := &utils.ErrorMessage{ - Code: v1.ErrNoIDInRequest, + Code: tmdsv1.ErrNoIDInRequest, Message: "CredentialsV2Request: No ID in the request", HTTPErrorCode: http.StatusBadRequest, } @@ -557,7 +558,7 @@ func TestCredentialsV2RequestWithNoArguments(t *testing.T) { // the credentials manager does not contain the credentials id specified in the query. func TestCredentialsV1RequestWhenCredentialsIdNotFound(t *testing.T) { expectedErrorMessage := &utils.ErrorMessage{ - Code: v1.ErrInvalidIDInRequest, + Code: tmdsv1.ErrInvalidIDInRequest, Message: fmt.Sprintf("CredentialsV1Request: Credentials not found"), HTTPErrorCode: http.StatusBadRequest, } @@ -571,7 +572,7 @@ func TestCredentialsV1RequestWhenCredentialsIdNotFound(t *testing.T) { // the credentials manager does not contain the credentials id specified in the query. func TestCredentialsV2RequestWhenCredentialsIdNotFound(t *testing.T) { expectedErrorMessage := &utils.ErrorMessage{ - Code: v1.ErrInvalidIDInRequest, + Code: tmdsv1.ErrInvalidIDInRequest, Message: fmt.Sprintf("CredentialsV2Request: Credentials not found"), HTTPErrorCode: http.StatusBadRequest, } @@ -585,7 +586,7 @@ func TestCredentialsV2RequestWhenCredentialsIdNotFound(t *testing.T) { // the credentials manager returns empty credentials. func TestCredentialsV1RequestWhenCredentialsUninitialized(t *testing.T) { expectedErrorMessage := &utils.ErrorMessage{ - Code: v1.ErrCredentialsUninitialized, + Code: tmdsv1.ErrCredentialsUninitialized, Message: fmt.Sprintf("CredentialsV1Request: Credentials uninitialized for ID"), HTTPErrorCode: http.StatusServiceUnavailable, } @@ -599,7 +600,7 @@ func TestCredentialsV1RequestWhenCredentialsUninitialized(t *testing.T) { // the credentials manager returns empty credentials. func TestCredentialsV2RequestWhenCredentialsUninitialized(t *testing.T) { expectedErrorMessage := &utils.ErrorMessage{ - Code: v1.ErrCredentialsUninitialized, + Code: tmdsv1.ErrCredentialsUninitialized, Message: fmt.Sprintf("CredentialsV2Request: Credentials uninitialized for ID"), HTTPErrorCode: http.StatusServiceUnavailable, } diff --git a/agent/logger/audit/audit_log_test.go b/agent/logger/audit/audit_log_test.go index 33a8919f990..24a93c65357 100644 --- a/agent/logger/audit/audit_log_test.go +++ b/agent/logger/audit/audit_log_test.go @@ -27,6 +27,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/config" mock_infologger "github.com/aws/amazon-ecs-agent/agent/logger/audit/mocks" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -77,7 +78,8 @@ func TestWritingToAuditLog(t *testing.T) { verifyAuditLogEntryResult(logLine, taskARN, dummyURLPath, t) }) - auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, GetCredentialsEventType(dummyRoleType)) + auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, + auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType)) } func TestWritingToAuditLogV2(t *testing.T) { @@ -108,7 +110,8 @@ func TestWritingToAuditLogV2(t *testing.T) { verifyAuditLogEntryResult(logLine, taskARN, credentials.V2CredentialsPath, t) }) - auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, GetCredentialsEventType(dummyRoleType)) + auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, + auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType)) } func TestWritingErrorsToAuditLog(t *testing.T) { @@ -139,7 +142,8 @@ func TestWritingErrorsToAuditLog(t *testing.T) { verifyAuditLogEntryResult(logLine, "-", dummyURLPath, t) }) - auditLogger.Log(request.LogRequest{Request: req, ARN: ""}, dummyResponseCode, GetCredentialsEventType(dummyRoleType)) + auditLogger.Log(request.LogRequest{Request: req, ARN: ""}, dummyResponseCode, + auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType)) } func TestWritingToAuditLogWhenDisabled(t *testing.T) { @@ -162,7 +166,8 @@ func TestWritingToAuditLogWhenDisabled(t *testing.T) { mockInfoLogger.EXPECT().Info(gomock.Any()).Times(0) - auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, GetCredentialsEventType(dummyRoleType)) + auditLogger.Log(request.LogRequest{Request: req, ARN: taskARN}, dummyResponseCode, + auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType)) } func TestConstructCommonAuditLogEntryFields(t *testing.T) { @@ -181,7 +186,8 @@ func TestConstructCommonAuditLogEntryFields(t *testing.T) { } func TestConstructAuditLogEntryByTypeGetCredentials(t *testing.T) { - result := constructAuditLogEntryByType(GetCredentialsEventType(dummyRoleType), dummyCluster, + result := constructAuditLogEntryByType( + auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType), dummyCluster, dummyContainerInstanceArn) verifyConstructAuditLogEntryGetCredentialsResult(result, t) } @@ -209,7 +215,8 @@ func verifyConstructAuditLogEntryGetCredentialsResult(result string, t *testing. tokens := strings.Split(result, " ") assert.Equal(t, getCredentialsEntryFieldCount, len(tokens), "Incorrect number of tokens in GetCredentials audit log entry") - assert.Equal(t, GetCredentialsEventType(dummyRoleType), tokens[0], "event type does not match") + assert.Equal(t, auditinterface.GetCredentialsEventTypeFromRoleType(dummyRoleType), + tokens[0], "event type does not match") auditLogVersion, _ := strconv.Atoi(tokens[1]) assert.Equal(t, getCredentialsAuditLogVersion, auditLogVersion, "version does not match") diff --git a/agent/logger/audit/entry_types.go b/agent/logger/audit/entry_types.go index c74ab221a46..d77c4d92581 100644 --- a/agent/logger/audit/entry_types.go +++ b/agent/logger/audit/entry_types.go @@ -19,15 +19,12 @@ import ( "time" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" log "github.com/cihub/seelog" ) const ( - getCredentialsEventType = "GetCredentials" - getCredentialsTaskExecutionEventType = "GetCredentialsExecutionRole" - getCredentialsInvalidRoleTypeEventType = "GetCredentialsInvalidRoleType" - // getCredentialsAuditLogVersion is the version of the audit log // Version '1', the fields are: // 1. event time @@ -56,18 +53,6 @@ type commonAuditLogEntryFields struct { arn string } -// GetCredentialsEventType is the type for a GetCredentials request -func GetCredentialsEventType(roleType string) string { - switch roleType { - case credentials.ApplicationRoleType: - return getCredentialsEventType - case credentials.ExecutionRoleType: - return getCredentialsTaskExecutionEventType - default: - return getCredentialsInvalidRoleTypeEventType - } -} - func (c *commonAuditLogEntryFields) string() string { return fmt.Sprintf("%s %d %s %s %s %s", c.eventTime, c.responseCode, c.srcAddr, c.theURL, c.userAgent, c.arn) } @@ -103,7 +88,7 @@ func constructCommonAuditLogEntryFields(r request.LogRequest, httpResponseCode i func constructAuditLogEntryByType(eventType string, cluster string, containerInstanceArn string) string { switch eventType { - case getCredentialsEventType: + case audit.GetCredentialsEventType: fields := &getCredentialsAuditLogEntryFields{ eventType: eventType, version: getCredentialsAuditLogVersion, @@ -111,7 +96,7 @@ func constructAuditLogEntryByType(eventType string, cluster string, containerIns containerInstanceArn: populateField(containerInstanceArn), } return fields.string() - case getCredentialsTaskExecutionEventType: + case audit.GetCredentialsTaskExecutionEventType: fields := &getCredentialsAuditLogEntryFields{ eventType: eventType, version: getCredentialsAuditLogVersion, diff --git a/agent/utils/utils.go b/agent/utils/utils.go index 95abb933649..0652a43b7b4 100644 --- a/agent/utils/utils.go +++ b/agent/utils/utils.go @@ -30,6 +30,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" "github.com/aws/amazon-ecs-agent/agent/utils/httpproxy" + commonutils "github.com/aws/amazon-ecs-agent/ecs-agent/utils" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/aws/awserr" @@ -44,28 +45,6 @@ func DefaultIfBlank(str string, default_value string) string { return str } -func ZeroOrNil(obj interface{}) bool { - value := reflect.ValueOf(obj) - if !value.IsValid() { - return true - } - if obj == nil { - return true - } - switch value.Kind() { - case reflect.Slice, reflect.Array, reflect.Map: - return value.Len() == 0 - } - zero := reflect.Zero(reflect.TypeOf(obj)) - if !value.Type().Comparable() { - return false - } - if obj == zero.Interface() { - return true - } - return false -} - // SlicesDeepEqual checks if slice1 and slice2 are equal, disregarding order. func SlicesDeepEqual(slice1, slice2 interface{}) bool { s1 := reflect.ValueOf(slice1) @@ -216,7 +195,7 @@ func SearchStrInDir(dir, filePrefix, content string) error { for _, file := range logfiles { if strings.HasPrefix(file.Name(), filePrefix) { desiredFile = file.Name() - if ZeroOrNil(desiredFile) { + if commonutils.ZeroOrNil(desiredFile) { return fmt.Errorf("File with prefix: %v does not exist", filePrefix) } diff --git a/agent/utils/utils_test.go b/agent/utils/utils_test.go index dc17d8395cf..5d5a180addc 100644 --- a/agent/utils/utils_test.go +++ b/agent/utils/utils_test.go @@ -17,7 +17,6 @@ package utils import ( - "encoding/json" "errors" "sort" "testing" @@ -39,52 +38,6 @@ func TestDefaultIfBlank(t *testing.T) { assert.Equal(t, defaultValue, result) } -type dummyStruct struct { - // no contents -} - -func (d dummyStruct) MarshalJSON([]byte, error) { - json.Marshal(nil) -} - -func TestZeroOrNil(t *testing.T) { - type ZeroTest struct { - testInt int - TestStr string - testNilJson dummyStruct - } - - var strMap map[string]string - - testCases := []struct { - param interface{} - expected bool - name string - }{ - {nil, true, "Nil is nil"}, - {0, true, "0 is 0"}, - {"", true, "\"\" is the string zerovalue"}, - {ZeroTest{}, true, "ZeroTest zero-value should be zero"}, - {ZeroTest{TestStr: "asdf"}, false, "ZeroTest with a field populated isn't zero"}, - {ZeroTest{testNilJson: dummyStruct{}}, true, "nil is nil"}, - {1, false, "1 is not 0"}, - {[]uint16{1, 2, 3}, false, "[1,2,3] is not zero"}, - {[]uint16{}, true, "[] is zero"}, - {struct{ uncomparable []uint16 }{uncomparable: []uint16{1, 2, 3}}, false, "Uncomparable structs are never zero"}, - {struct{ uncomparable []uint16 }{uncomparable: nil}, false, "Uncomparable structs are never zero"}, - {strMap, true, "map[string]string is zero or nil"}, - {make(map[string]string), true, "empty map[string]string is zero or nil"}, - {map[string]string{"foo": "bar"}, false, "map[string]string{foo:bar} is not zero or nil"}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, ZeroOrNil(tc.param), tc.name) - }) - } - -} - func TestSlicesDeepEqual(t *testing.T) { testCases := []struct { left []string diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/audit_log.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/audit_log.go index 00a3ff5aaf9..1f503d22f5a 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/audit_log.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/audit_log.go @@ -15,10 +15,31 @@ package audit -import "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" +import ( + "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" +) + +const ( + GetCredentialsEventType = "GetCredentials" + GetCredentialsTaskExecutionEventType = "GetCredentialsExecutionRole" + GetCredentialsInvalidRoleTypeEventType = "GetCredentialsInvalidRoleType" +) type AuditLogger interface { Log(r request.LogRequest, httpResponseCode int, eventType string) GetContainerInstanceArn() string GetCluster() string } + +// Returns a suitable audit log event type for the credentials role type +func GetCredentialsEventTypeFromRoleType(roleType string) string { + switch roleType { + case credentials.ApplicationRoleType: + return GetCredentialsEventType + case credentials.ExecutionRoleType: + return GetCredentialsTaskExecutionEventType + default: + return GetCredentialsInvalidRoleTypeEventType + } +} diff --git a/agent/handlers/v1/credentials_handler.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1/credentials_handler.go similarity index 81% rename from agent/handlers/v1/credentials_handler.go rename to agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1/credentials_handler.go index 080b6635882..90af17509a9 100644 --- a/agent/handlers/v1/credentials_handler.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1/credentials_handler.go @@ -19,12 +19,12 @@ import ( "fmt" "net/http" - "github.com/aws/amazon-ecs-agent/agent/logger/audit" - "github.com/aws/amazon-ecs-agent/agent/utils" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" handlersutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/utils" "github.com/cihub/seelog" ) @@ -58,7 +58,10 @@ const ( // CredentialsHandler creates response for the 'v1/credentials' API. It returns a JSON response // containing credentials when found. The HTTP status code of 400 is returned otherwise. -func CredentialsHandler(credentialsManager credentials.Manager, auditLogger auditinterface.AuditLogger) func(http.ResponseWriter, *http.Request) { +func CredentialsHandler( + credentialsManager credentials.Manager, + auditLogger auditinterface.AuditLogger, +) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { credentialsID := getCredentialsID(r) errPrefix := fmt.Sprintf("CredentialsV%dRequest: ", apiVersion) @@ -68,22 +71,38 @@ func CredentialsHandler(credentialsManager credentials.Manager, auditLogger audi // CredentialsHandlerImpl is the major logic in CredentialsHandler, abstract this out // because v2.CredentialsHandler also uses the same logic. -func CredentialsHandlerImpl(w http.ResponseWriter, r *http.Request, auditLogger auditinterface.AuditLogger, credentialsManager credentials.Manager, credentialsID string, errPrefix string) { - responseJSON, arn, roleType, errorMessage, err := processCredentialsRequest(credentialsManager, r, credentialsID, errPrefix) +func CredentialsHandlerImpl( + w http.ResponseWriter, + r *http.Request, + auditLogger auditinterface.AuditLogger, + credentialsManager credentials.Manager, + credentialsID string, + errPrefix string, +) { + responseJSON, arn, roleType, errorMessage, err := processCredentialsRequest( + credentialsManager, r, credentialsID, errPrefix) if err != nil { errResponseJSON, err := json.Marshal(errorMessage) if e := handlersutils.WriteResponseIfMarshalError(w, err); e != nil { return } - writeCredentialsRequestResponse(w, r, errorMessage.HTTPErrorCode, audit.GetCredentialsEventType(roleType), arn, auditLogger, errResponseJSON) + writeCredentialsRequestResponse(w, r, errorMessage.HTTPErrorCode, + audit.GetCredentialsEventTypeFromRoleType(roleType), arn, auditLogger, errResponseJSON) return } - writeCredentialsRequestResponse(w, r, http.StatusOK, audit.GetCredentialsEventType(roleType), arn, auditLogger, responseJSON) + writeCredentialsRequestResponse(w, r, http.StatusOK, + audit.GetCredentialsEventTypeFromRoleType(roleType), arn, auditLogger, responseJSON) } -// processCredentialsRequest returns the response json containing credentials for the credentials id in the request -func processCredentialsRequest(credentialsManager credentials.Manager, r *http.Request, credentialsID string, errPrefix string) ([]byte, string, string, *handlersutils.ErrorMessage, error) { +// processCredentialsRequest returns the response json containing credentials for the +// credentials id in the request +func processCredentialsRequest( + credentialsManager credentials.Manager, + r *http.Request, + credentialsID string, + errPrefix string, +) ([]byte, string, string, *handlersutils.ErrorMessage, error) { if credentialsID == "" { errText := errPrefix + "No Credential ID in the request" seelog.Errorf("Error processing credential request: %s", errText) @@ -140,9 +159,16 @@ func processCredentialsRequest(credentialsManager credentials.Manager, r *http.R return credentialsJSON, credentials.ARN, credentials.IAMRoleCredentials.RoleType, nil, nil } -func writeCredentialsRequestResponse(w http.ResponseWriter, r *http.Request, httpStatusCode int, eventType string, arn string, auditLogger auditinterface.AuditLogger, message []byte) { +func writeCredentialsRequestResponse( + w http.ResponseWriter, + r *http.Request, + httpStatusCode int, + eventType string, + arn string, + auditLogger auditinterface.AuditLogger, + message []byte, +) { auditLogger.Log(request.LogRequest{Request: r, ARN: arn}, httpStatusCode, eventType) - handlersutils.WriteJSONToResponse(w, httpStatusCode, message, handlersutils.RequestTypeCreds) } diff --git a/agent/handlers/v2/credentials_handler.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2/credentials_handler.go similarity index 97% rename from agent/handlers/v2/credentials_handler.go rename to agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2/credentials_handler.go index fbafa14a55c..70c60f95619 100644 --- a/agent/handlers/v2/credentials_handler.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2/credentials_handler.go @@ -17,10 +17,10 @@ import ( "fmt" "net/http" - v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + v1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" "github.com/gorilla/mux" ) diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/utils/utils.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/utils/utils.go new file mode 100644 index 00000000000..7ac89d29750 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/utils/utils.go @@ -0,0 +1,37 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +package utils + +import "reflect" + +func ZeroOrNil(obj interface{}) bool { + value := reflect.ValueOf(obj) + if !value.IsValid() { + return true + } + if obj == nil { + return true + } + switch value.Kind() { + case reflect.Slice, reflect.Array, reflect.Map: + return value.Len() == 0 + } + zero := reflect.Zero(reflect.TypeOf(obj)) + if !value.Type().Comparable() { + return false + } + if obj == zero.Interface() { + return true + } + return false +} diff --git a/agent/vendor/modules.txt b/agent/vendor/modules.txt index 1a121666712..6a041f65c57 100644 --- a/agent/vendor/modules.txt +++ b/agent/vendor/modules.txt @@ -22,8 +22,11 @@ github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request github.com/aws/amazon-ecs-agent/ecs-agent/logger/field github.com/aws/amazon-ecs-agent/ecs-agent/tmds github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils +github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1 +github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2 github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging github.com/aws/amazon-ecs-agent/ecs-agent/tmds/utils/mux +github.com/aws/amazon-ecs-agent/ecs-agent/utils github.com/aws/amazon-ecs-agent/ecs-agent/utils/arn github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime/mocks diff --git a/ecs-agent/logger/audit/audit_log.go b/ecs-agent/logger/audit/audit_log.go index 00a3ff5aaf9..1f503d22f5a 100644 --- a/ecs-agent/logger/audit/audit_log.go +++ b/ecs-agent/logger/audit/audit_log.go @@ -15,10 +15,31 @@ package audit -import "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" +import ( + "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" +) + +const ( + GetCredentialsEventType = "GetCredentials" + GetCredentialsTaskExecutionEventType = "GetCredentialsExecutionRole" + GetCredentialsInvalidRoleTypeEventType = "GetCredentialsInvalidRoleType" +) type AuditLogger interface { Log(r request.LogRequest, httpResponseCode int, eventType string) GetContainerInstanceArn() string GetCluster() string } + +// Returns a suitable audit log event type for the credentials role type +func GetCredentialsEventTypeFromRoleType(roleType string) string { + switch roleType { + case credentials.ApplicationRoleType: + return GetCredentialsEventType + case credentials.ExecutionRoleType: + return GetCredentialsTaskExecutionEventType + default: + return GetCredentialsInvalidRoleTypeEventType + } +} diff --git a/ecs-agent/tmds/handlers/credentials_handler_test.go b/ecs-agent/tmds/handlers/credentials_handler_test.go new file mode 100644 index 00000000000..a9c56c41caf --- /dev/null +++ b/ecs-agent/tmds/handlers/credentials_handler_test.go @@ -0,0 +1,312 @@ +//go:build unit +// +build unit + +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + mock_credentials "github.com/aws/amazon-ecs-agent/ecs-agent/credentials/mocks" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + mock_audit "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + v1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" + v2 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2" + "github.com/gorilla/mux" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Function to make a URL path for credentials request +type MakePath = func(credsId string) string + +// A function that creates a credentialse HTTP handler +type GetCredentialsHandler = func(credentials.Manager, audit.AuditLogger) http.Handler + +// A structure representing a test case for credentials endpoint error handling tests. +type CredentialsErrorTestCase struct { + Name string + Path string + GetHandler func(*mock_credentials.MockManager, *mock_audit.MockAuditLogger) http.Handler + ExpectedStatusCode int + ExpectedResponse utils.ErrorMessage +} + +// MakePath function for credentials endpoint v1 +var makePathV1 MakePath = func(credsId string) string { + if credsId == "" { + return "/credentials" + } + return "/credentials?id=" + credsId +} + +// MakePath function for credentials endpoint v2 +var makePathV2 MakePath = func(credsId string) string { + return fmt.Sprintf("%s/%s", credentials.V2CredentialsPath, credsId) +} + +// GetCredentialsHandler function for v1 +var getCredentialsHandlerV1 GetCredentialsHandler = func( + credManager credentials.Manager, + auditLogger audit.AuditLogger, +) http.Handler { + return http.HandlerFunc(v1.CredentialsHandler(credManager, auditLogger)) +} + +// GetCredentialsHandler function for v2 +var getCredentialsHandlerV2 GetCredentialsHandler = func( + credManager credentials.Manager, + auditLogger audit.AuditLogger, +) http.Handler { + router := mux.NewRouter() + router.HandleFunc(v2.CredentialsPath, v2.CredentialsHandler(credManager, auditLogger)) + return router +} + +// Creates a test case for "no credentials ID in request" error +func noCredentialsIDCase( + makePath MakePath, + makeHandler GetCredentialsHandler, + errorPrefix string, +) CredentialsErrorTestCase { + return CredentialsErrorTestCase{ + Name: "no credentials ID in the requeest", + Path: makePath(""), + GetHandler: func( + credManager *mock_credentials.MockManager, + auditLogger *mock_audit.MockAuditLogger, + ) http.Handler { + auditLogger.EXPECT().Log( + gomock.Any(), + http.StatusBadRequest, + audit.GetCredentialsInvalidRoleTypeEventType) + + return makeHandler(credManager, auditLogger) + }, + ExpectedStatusCode: http.StatusBadRequest, + ExpectedResponse: utils.ErrorMessage{ + Code: v1.ErrNoIDInRequest, + Message: errorPrefix + ": No Credential ID in the request", + HTTPErrorCode: http.StatusBadRequest, + }, + } +} + +// Creates a test case for "credentials not found" error +func credentialsNotFoundCase( + makePath MakePath, + makeHandler GetCredentialsHandler, + errorPrefix string, +) CredentialsErrorTestCase { + return CredentialsErrorTestCase{ + Name: "credentials not found", + Path: makePath("credsid"), + GetHandler: func( + credManager *mock_credentials.MockManager, + auditLogger *mock_audit.MockAuditLogger, + ) http.Handler { + auditLogger.EXPECT().Log( + gomock.Any(), + http.StatusBadRequest, + audit.GetCredentialsInvalidRoleTypeEventType) + credManager.EXPECT().GetTaskCredentials("credsid"). + Return(credentials.TaskIAMRoleCredentials{}, false) + + return makeHandler(credManager, auditLogger) + }, + ExpectedStatusCode: http.StatusBadRequest, + ExpectedResponse: utils.ErrorMessage{ + Code: v1.ErrInvalidIDInRequest, + Message: errorPrefix + ": Credentials not found", + HTTPErrorCode: http.StatusBadRequest, + }, + } +} + +// Creates a test case for "credentials uninitialized" error +func credentialsUninitializedCase( + makePath MakePath, + makeHandler GetCredentialsHandler, + errorPrefix string, +) CredentialsErrorTestCase { + return CredentialsErrorTestCase{ + Name: "credentials uninitialized", + Path: makePath("credsid"), + GetHandler: func( + credManager *mock_credentials.MockManager, + auditLogger *mock_audit.MockAuditLogger, + ) http.Handler { + auditLogger.EXPECT().Log( + gomock.Any(), + http.StatusServiceUnavailable, + audit.GetCredentialsInvalidRoleTypeEventType) + credManager.EXPECT().GetTaskCredentials("credsid"). + Return(credentials.TaskIAMRoleCredentials{}, true) + + return makeHandler(credManager, auditLogger) + }, + ExpectedStatusCode: http.StatusServiceUnavailable, + ExpectedResponse: utils.ErrorMessage{ + Code: v1.ErrCredentialsUninitialized, + Message: errorPrefix + ": Credentials uninitialized for ID", + HTTPErrorCode: http.StatusServiceUnavailable, + }, + } +} + +// Tests error cases for credentials endpoint v1 +func TestCredentialsHandlerErrorV1(t *testing.T) { + errorPrefix := "CredentialsV1Request" + tcs := []CredentialsErrorTestCase{ + noCredentialsIDCase(makePathV1, getCredentialsHandlerV1, errorPrefix), + credentialsNotFoundCase(makePathV1, getCredentialsHandlerV1, errorPrefix), + credentialsUninitializedCase(makePathV1, getCredentialsHandlerV1, errorPrefix), + } + for _, tc := range tcs { + t.Run(tc.Name, func(t *testing.T) { + testCredentialsHandlerError(t, tc) + }) + } +} + +// Tests error cases for credentials endpoint v2 +func TestCredentialsHandlerErrorV2(t *testing.T) { + errorPrefix := "CredentialsV2Request" + tcs := []CredentialsErrorTestCase{ + noCredentialsIDCase(makePathV2, getCredentialsHandlerV2, errorPrefix), + credentialsNotFoundCase(makePathV2, getCredentialsHandlerV2, errorPrefix), + credentialsUninitializedCase(makePathV2, getCredentialsHandlerV2, errorPrefix), + } + for _, tc := range tcs { + t.Run(tc.Name, func(t *testing.T) { + testCredentialsHandlerError(t, tc) + }) + } +} + +// Tests error handling of credentials endpoint for a given test case. +// This function works by sending a test request to the +// handler and asserting on the response code and response body. +// This function also creates mocks for credentials manager and audit logger interfaces +// which are passed to the provided test case. The test case is responsible for setting up +// expectations on the mocks. +func testCredentialsHandlerError( + t *testing.T, + tc CredentialsErrorTestCase, +) { + // Set up mocks + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + auditLogger := mock_audit.NewMockAuditLogger(ctrl) + credManager := mock_credentials.NewMockManager(ctrl) + + // Create request handler using a function provided by the test case. + // The test case is responsible for setting up expectations on the mocks. + handler := tc.GetHandler(credManager, auditLogger) + + // Send request and read response + recorder := recordCredentialsRequest(t, handler, tc.Path) + var response utils.ErrorMessage + err := json.Unmarshal(recorder.Body.Bytes(), &response) + require.NoError(t, err) + + // Assert on response status code and body + assert.Equal(t, tc.ExpectedStatusCode, recorder.Code) + assert.Equal(t, tc.ExpectedResponse, response) +} + +// Tests happy case for credentials endpoint v1 +func TestCredentialsHandlerV1Success(t *testing.T) { + testCredentialsHandlerSuccess(t, makePathV1, getCredentialsHandlerV1) +} + +// Tests happy case for credentials endpoint v2 +func TestCredentialsHandlerV2Success(t *testing.T) { + testCredentialsHandlerSuccess(t, makePathV2, getCredentialsHandlerV2) +} + +// Tests happy case for credentials endpoint by sending a request to the handler and +// asserting that 200-OK response is received with credentials in the body. +func testCredentialsHandlerSuccess(t *testing.T, makePath MakePath, makeHandler GetCredentialsHandler) { + // Create mocks + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + auditLogger := mock_audit.NewMockAuditLogger(ctrl) + credManager := mock_credentials.NewMockManager(ctrl) + + // Some variables + credsId := "credsid" + taskArn := "taskArn" + path := makePath(credsId) + + // Credentials that will be found by the credential manager + creds := credentials.IAMRoleCredentials{ + CredentialsID: credsId, + RoleArn: "rolearn", + AccessKeyID: "access_key_id", + SecretAccessKey: "secret_access_key", + SessionToken: "session_token", + Expiration: "expiration", + RoleType: credentials.ApplicationRoleType, + } + + // Expected response - not all credentials fields are sent in the response + expectedCreds := credentials.IAMRoleCredentials{ + RoleArn: "rolearn", + AccessKeyID: "access_key_id", + SecretAccessKey: "secret_access_key", + SessionToken: "session_token", + Expiration: "expiration", + } + + auditLogger.EXPECT().Log( + gomock.Any(), + http.StatusOK, + audit.GetCredentialsEventType) + credManager.EXPECT().GetTaskCredentials(credsId).Return( + credentials.TaskIAMRoleCredentials{ARN: taskArn, IAMRoleCredentials: creds}, true) + + // Prepare and send a request + handler := makeHandler(credManager, auditLogger) + recorder := recordCredentialsRequest(t, handler, path) + + // Read the response + var response credentials.IAMRoleCredentials + err := json.Unmarshal(recorder.Body.Bytes(), &response) + require.NoError(t, err) + + // Assert on status code and body + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, expectedCreds, response) +} + +// Sends a request to the handler and records it +func recordCredentialsRequest(t *testing.T, handler http.Handler, path string) *httptest.ResponseRecorder { + // Prepare and send a request + req, err := http.NewRequest("GET", path, nil) + require.NoError(t, err) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + return recorder +} diff --git a/ecs-agent/tmds/handlers/v1/credentials_handler.go b/ecs-agent/tmds/handlers/v1/credentials_handler.go new file mode 100644 index 00000000000..90af17509a9 --- /dev/null +++ b/ecs-agent/tmds/handlers/v1/credentials_handler.go @@ -0,0 +1,181 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package v1 + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request" + handlersutils "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + "github.com/aws/amazon-ecs-agent/ecs-agent/utils" + "github.com/cihub/seelog" +) + +const ( + // Error Types + + // ErrNoIDInRequest is the error code indicating that no ID was specified + ErrNoIDInRequest = "NoIdInRequest" + + // ErrInvalidIDInRequest is the error code indicating that the ID was invalid + ErrInvalidIDInRequest = "InvalidIdInRequest" + + // ErrNoCredentialsAssociated is the error code indicating no credentials are + // associated with the specified ID + ErrNoCredentialsAssociated = "NoCredentialsAssociated" + + // ErrCredentialsUninitialized is the error code indicating that credentials were + // not properly initialized. This may happen immediately after the agent is + // started, before it has completed state reconciliation. + ErrCredentialsUninitialized = "CredentialsUninitialized" + + // ErrInternalServer is the error indicating something generic went wrong + ErrInternalServer = "InternalServerError" + + // Credentials API version. + apiVersion = 1 + + // CredentialsPath specifies the relative URI path for serving task IAM credentials + CredentialsPath = credentials.V1CredentialsPath +) + +// CredentialsHandler creates response for the 'v1/credentials' API. It returns a JSON response +// containing credentials when found. The HTTP status code of 400 is returned otherwise. +func CredentialsHandler( + credentialsManager credentials.Manager, + auditLogger auditinterface.AuditLogger, +) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + credentialsID := getCredentialsID(r) + errPrefix := fmt.Sprintf("CredentialsV%dRequest: ", apiVersion) + CredentialsHandlerImpl(w, r, auditLogger, credentialsManager, credentialsID, errPrefix) + } +} + +// CredentialsHandlerImpl is the major logic in CredentialsHandler, abstract this out +// because v2.CredentialsHandler also uses the same logic. +func CredentialsHandlerImpl( + w http.ResponseWriter, + r *http.Request, + auditLogger auditinterface.AuditLogger, + credentialsManager credentials.Manager, + credentialsID string, + errPrefix string, +) { + responseJSON, arn, roleType, errorMessage, err := processCredentialsRequest( + credentialsManager, r, credentialsID, errPrefix) + if err != nil { + errResponseJSON, err := json.Marshal(errorMessage) + if e := handlersutils.WriteResponseIfMarshalError(w, err); e != nil { + return + } + writeCredentialsRequestResponse(w, r, errorMessage.HTTPErrorCode, + audit.GetCredentialsEventTypeFromRoleType(roleType), arn, auditLogger, errResponseJSON) + return + } + + writeCredentialsRequestResponse(w, r, http.StatusOK, + audit.GetCredentialsEventTypeFromRoleType(roleType), arn, auditLogger, responseJSON) +} + +// processCredentialsRequest returns the response json containing credentials for the +// credentials id in the request +func processCredentialsRequest( + credentialsManager credentials.Manager, + r *http.Request, + credentialsID string, + errPrefix string, +) ([]byte, string, string, *handlersutils.ErrorMessage, error) { + if credentialsID == "" { + errText := errPrefix + "No Credential ID in the request" + seelog.Errorf("Error processing credential request: %s", errText) + msg := &handlersutils.ErrorMessage{ + Code: ErrNoIDInRequest, + Message: errText, + HTTPErrorCode: http.StatusBadRequest, + } + return nil, "", "", msg, errors.New(errText) + } + + credentials, ok := credentialsManager.GetTaskCredentials(credentialsID) + if !ok { + errText := errPrefix + "Credentials not found" + seelog.Errorf("Error processing credential request: %s", errText) + msg := &handlersutils.ErrorMessage{ + Code: ErrInvalidIDInRequest, + Message: errText, + HTTPErrorCode: http.StatusBadRequest, + } + return nil, "", "", msg, errors.New(errText) + } + + seelog.Infof("Processing credential request, credentialType=%s taskARN=%s", + credentials.IAMRoleCredentials.RoleType, credentials.ARN) + + if utils.ZeroOrNil(credentials.ARN) && utils.ZeroOrNil(credentials.IAMRoleCredentials) { + // This can happen when the agent is restarted and is reconciling its state. + errText := errPrefix + "Credentials uninitialized for ID" + seelog.Errorf("Error processing credential request credentialType=%s taskARN=%s: %s", + credentials.IAMRoleCredentials.RoleType, credentials.ARN, errText) + msg := &handlersutils.ErrorMessage{ + Code: ErrCredentialsUninitialized, + Message: errText, + HTTPErrorCode: http.StatusServiceUnavailable, + } + return nil, "", "", msg, errors.New(errText) + } + + credentialsJSON, err := json.Marshal(credentials.IAMRoleCredentials) + if err != nil { + errText := errPrefix + "Error marshaling credentials" + seelog.Errorf("Error processing credential request credentialType=%s taskARN=%s: %s", + credentials.IAMRoleCredentials.RoleType, credentials.ARN, errText) + msg := &handlersutils.ErrorMessage{ + Code: ErrInternalServer, + Message: "Internal server error", + HTTPErrorCode: http.StatusInternalServerError, + } + return nil, "", "", msg, errors.New(errText) + } + + // Success + return credentialsJSON, credentials.ARN, credentials.IAMRoleCredentials.RoleType, nil, nil +} + +func writeCredentialsRequestResponse( + w http.ResponseWriter, + r *http.Request, + httpStatusCode int, + eventType string, + arn string, + auditLogger auditinterface.AuditLogger, + message []byte, +) { + auditLogger.Log(request.LogRequest{Request: r, ARN: arn}, httpStatusCode, eventType) + handlersutils.WriteJSONToResponse(w, httpStatusCode, message, handlersutils.RequestTypeCreds) +} + +func getCredentialsID(r *http.Request) string { + credentialsID, ok := handlersutils.ValueFromRequest(r, credentials.CredentialsIDQueryParameterName) + if ok { + return credentialsID + } + return "" +} diff --git a/ecs-agent/tmds/handlers/v2/credentials_handler.go b/ecs-agent/tmds/handlers/v2/credentials_handler.go new file mode 100644 index 00000000000..70c60f95619 --- /dev/null +++ b/ecs-agent/tmds/handlers/v2/credentials_handler.go @@ -0,0 +1,54 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package v2 + +import ( + "fmt" + "net/http" + + "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" + auditinterface "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit" + "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" + v1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" + "github.com/gorilla/mux" +) + +const ( + // Credentials API version. + apiVersion = 2 + + // credentialsIDMuxName is the key that's used in gorilla/mux to get the credentials ID. + credentialsIDMuxName = "credentialsIDMuxName" +) + +// CredentialsPath specifies the relative URI path for serving task IAM credentials. +// Use "AnythingRegEx" regex to handle the case where the "credentialsIDMuxName" is +// empty, this is because the name that's used to extract dynamic value in gorilla cannot +// be empty by default. If we don't do this, we will get 404 error when we access "/v2/credentials/", +// but it should be 400 error. +var CredentialsPath = credentials.V2CredentialsPath + "/" + utils.ConstructMuxVar(credentialsIDMuxName, utils.AnythingRegEx) + +// CredentialsHandler creates response for the 'v2/credentials' API. +func CredentialsHandler(credentialsManager credentials.Manager, auditLogger auditinterface.AuditLogger) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + credentialsID := getCredentialsID(r) + errPrefix := fmt.Sprintf("CredentialsV%dRequest: ", apiVersion) + v1.CredentialsHandlerImpl(w, r, auditLogger, credentialsManager, credentialsID, errPrefix) + } +} + +func getCredentialsID(r *http.Request) string { + vars := mux.Vars(r) + return vars[credentialsIDMuxName] +} diff --git a/ecs-agent/utils/utils.go b/ecs-agent/utils/utils.go new file mode 100644 index 00000000000..7ac89d29750 --- /dev/null +++ b/ecs-agent/utils/utils.go @@ -0,0 +1,37 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +package utils + +import "reflect" + +func ZeroOrNil(obj interface{}) bool { + value := reflect.ValueOf(obj) + if !value.IsValid() { + return true + } + if obj == nil { + return true + } + switch value.Kind() { + case reflect.Slice, reflect.Array, reflect.Map: + return value.Len() == 0 + } + zero := reflect.Zero(reflect.TypeOf(obj)) + if !value.Type().Comparable() { + return false + } + if obj == zero.Interface() { + return true + } + return false +} diff --git a/ecs-agent/utils/utils_test.go b/ecs-agent/utils/utils_test.go new file mode 100644 index 00000000000..2ffecf4fae0 --- /dev/null +++ b/ecs-agent/utils/utils_test.go @@ -0,0 +1,61 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type dummyStruct struct { + // no contents +} + +func TestZeroOrNil(t *testing.T) { + type ZeroTest struct { + testInt int + TestStr string + testNilJson dummyStruct + } + + var strMap map[string]string + + testCases := []struct { + param interface{} + expected bool + name string + }{ + {nil, true, "Nil is nil"}, + {0, true, "0 is 0"}, + {"", true, "\"\" is the string zerovalue"}, + {ZeroTest{}, true, "ZeroTest zero-value should be zero"}, + {ZeroTest{TestStr: "asdf"}, false, "ZeroTest with a field populated isn't zero"}, + {ZeroTest{testNilJson: dummyStruct{}}, true, "nil is nil"}, + {1, false, "1 is not 0"}, + {[]uint16{1, 2, 3}, false, "[1,2,3] is not zero"}, + {[]uint16{}, true, "[] is zero"}, + {struct{ uncomparable []uint16 }{uncomparable: []uint16{1, 2, 3}}, false, "Uncomparable structs are never zero"}, + {struct{ uncomparable []uint16 }{uncomparable: nil}, false, "Uncomparable structs are never zero"}, + {strMap, true, "map[string]string is zero or nil"}, + {make(map[string]string), true, "empty map[string]string is zero or nil"}, + {map[string]string{"foo": "bar"}, false, "map[string]string{foo:bar} is not zero or nil"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, ZeroOrNil(tc.param), tc.name) + }) + } + +} From edbeee3fc25373f0e4abb13f045767b179bcff32 Mon Sep 17 00:00:00 2001 From: Amogh Rathore Date: Thu, 18 May 2023 14:24:11 -0700 Subject: [PATCH 9/9] Move some common metadata models to ecs-agent module (#3701) --- agent/containermetadata/parse_metadata.go | 7 +-- agent/containermetadata/types.go | 12 ++--- agent/handlers/task_server_setup_test.go | 19 ++++---- agent/handlers/v1/response.go | 44 ++++++----------- agent/handlers/v2/response.go | 48 +++++++++---------- .../handlers/v3/container_metadata_handler.go | 10 ++-- .../handlers/v4/container_metadata_handler.go | 6 +-- agent/handlers/v4/response.go | 6 +-- .../tmds/handlers/response/response.go | 38 +++++++++++++++ agent/vendor/modules.txt | 1 + ecs-agent/tmds/handlers/response/response.go | 38 +++++++++++++++ 11 files changed, 142 insertions(+), 87 deletions(-) create mode 100644 agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response/response.go create mode 100644 ecs-agent/tmds/handlers/response/response.go diff --git a/agent/containermetadata/parse_metadata.go b/agent/containermetadata/parse_metadata.go index 96d45148727..e979fd48e46 100644 --- a/agent/containermetadata/parse_metadata.go +++ b/agent/containermetadata/parse_metadata.go @@ -19,6 +19,7 @@ import ( apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" apitask "github.com/aws/amazon-ecs-agent/agent/api/task" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/cihub/seelog" "github.com/docker/docker/api/types" dockercontainer "github.com/docker/docker/api/types/container" @@ -142,17 +143,17 @@ func parseNetworkMetadata(settings *types.NetworkSettings, hostConfig *dockercon // Extensive Network information is not available for Docker API versions 1.17-1.20 // Instead we only get the details of the first network - networkList := make([]Network, 0) + networkList := make([]tmdsresponse.Network, 0) if len(settings.Networks) > 0 { for modeFromSettings, containerNetwork := range settings.Networks { networkMode := modeFromSettings ipv4Addresses := []string{containerNetwork.IPAddress} - network := Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses} + network := tmdsresponse.Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses} networkList = append(networkList, network) } } else { ipv4Addresses := []string{ipv4AddressFromSettings} - network := Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses} + network := tmdsresponse.Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses} networkList = append(networkList, network) } diff --git a/agent/containermetadata/types.go b/agent/containermetadata/types.go index e3276a52c10..2b7c8353045 100644 --- a/agent/containermetadata/types.go +++ b/agent/containermetadata/types.go @@ -20,6 +20,7 @@ import ( "time" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/docker/docker/api/types" ) @@ -82,19 +83,12 @@ type DockerMetadataClient interface { InspectContainer(context.Context, string, time.Duration) (*types.ContainerJSON, error) } -// Network is a struct that keeps track of metadata of a network interface -type Network struct { - NetworkMode string `json:"NetworkMode,omitempty"` - IPv4Addresses []string `json:"IPv4Addresses,omitempty"` - IPv6Addresses []string `json:"IPv6Addresses,omitempty"` -} - // NetworkMetadata keeps track of the data we parse from the Network Settings // in docker containers. While most information is redundant with the internal // Network struct, we keeps this wrapper in case we wish to add data specifically // from the NetworkSettings type NetworkMetadata struct { - networks []Network + networks []tmdsresponse.Network } // DockerContainerMetadata keeps track of all metadata acquired from Docker inspection @@ -147,7 +141,7 @@ type metadataSerializer struct { ImageID string `json:"ImageID,omitempty"` ImageName string `json:"ImageName,omitempty"` Ports []apicontainer.PortBinding `json:"PortMappings,omitempty"` - Networks []Network `json:"Networks,omitempty"` + Networks []tmdsresponse.Network `json:"Networks,omitempty"` MetadataFileStatus MetadataStatus `json:"MetadataFileStatus,omitempty"` AvailabilityZone string `json:"AvailabilityZone,omitempty"` HostPrivateIPv4Address string `json:"HostPrivateIPv4Address,omitempty"` diff --git a/agent/handlers/task_server_setup_test.go b/agent/handlers/task_server_setup_test.go index 2e8807617db..ecda0e81dc8 100644 --- a/agent/handlers/task_server_setup_test.go +++ b/agent/handlers/task_server_setup_test.go @@ -35,11 +35,9 @@ import ( apitask "github.com/aws/amazon-ecs-agent/agent/api/task" apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status" "github.com/aws/amazon-ecs-agent/agent/config" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" mock_dockerstate "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate/mocks" task_protection_v1 "github.com/aws/amazon-ecs-agent/agent/handlers/agentapi/taskprotection/v1/handlers" - v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" v4 "github.com/aws/amazon-ecs-agent/agent/handlers/v4" @@ -50,6 +48,7 @@ import ( "github.com/aws/amazon-ecs-agent/ecs-agent/credentials" mock_credentials "github.com/aws/amazon-ecs-agent/ecs-agent/credentials/mocks" mock_audit "github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" tmdsv1 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1" "github.com/aws/aws-sdk-go/aws" @@ -242,14 +241,14 @@ var ( }, Type: containerType, Labels: labels, - Ports: []v1.PortResponse{ + Ports: []tmdsresponse.PortResponse{ { ContainerPort: containerPort, Protocol: containerPortProtocol, HostPort: containerPort, }, }, - Networks: []containermetadata.Network{ + Networks: []tmdsresponse.Network{ { NetworkMode: utils.NetworkModeAWSVPC, IPv4Addresses: []string{eniIPv4Address}, @@ -348,13 +347,13 @@ var ( }, Type: containerType, Labels: labels, - Ports: []v1.PortResponse{ + Ports: []tmdsresponse.PortResponse{ { ContainerPort: containerPort, Protocol: containerPortProtocol, }, }, - Networks: []containermetadata.Network{ + Networks: []tmdsresponse.Network{ { NetworkMode: bridgeMode, IPv4Addresses: []string{bridgeIPAddr}, @@ -395,14 +394,14 @@ var ( }, Type: containerType, Labels: labels, - Ports: []v1.PortResponse{ + Ports: []tmdsresponse.PortResponse{ { ContainerPort: containerPort, Protocol: containerPortProtocol, HostPort: containerPort, }, }, - Networks: []containermetadata.Network{ + Networks: []tmdsresponse.Network{ { NetworkMode: utils.NetworkModeAWSVPC, IPv4Addresses: []string{eniIPv4Address}, @@ -410,7 +409,7 @@ var ( }, }, Networks: []v4.Network{{ - Network: containermetadata.Network{ + Network: tmdsresponse.Network{ NetworkMode: utils.NetworkModeAWSVPC, IPv4Addresses: []string{eniIPv4Address}, }, @@ -485,7 +484,7 @@ var ( expectedV4BridgeContainerResponse = v4.ContainerResponse{ ContainerResponse: &expectedBridgeContainerResponse, Networks: []v4.Network{{ - Network: containermetadata.Network{ + Network: tmdsresponse.Network{ NetworkMode: bridgeMode, IPv4Addresses: []string{bridgeIPAddr}, }, diff --git a/agent/handlers/v1/response.go b/agent/handlers/v1/response.go index 43ce3a55aaa..0284e40ff9f 100644 --- a/agent/handlers/v1/response.go +++ b/agent/handlers/v1/response.go @@ -16,9 +16,9 @@ package v1 import ( apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" apitask "github.com/aws/amazon-ecs-agent/agent/api/task" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" ) @@ -46,28 +46,12 @@ type TasksResponse struct { // ContainerResponse is the schema for the container response JSON object type ContainerResponse struct { - DockerID string `json:"DockerId"` - DockerName string `json:"DockerName"` - Name string `json:"Name"` - Ports []PortResponse `json:"Ports,omitempty"` - Networks []containermetadata.Network `json:"Networks,omitempty"` - Volumes []VolumeResponse `json:"Volumes,omitempty"` -} - -// VolumeResponse is the schema for the volume response JSON object -type VolumeResponse struct { - DockerName string `json:"DockerName,omitempty"` - Source string `json:"Source,omitempty"` - Destination string `json:"Destination,omitempty"` -} - -// PortResponse defines the schema for portmapping response JSON -// object. -type PortResponse struct { - ContainerPort uint16 `json:"ContainerPort,omitempty"` - Protocol string `json:"Protocol,omitempty"` - HostPort uint16 `json:"HostPort,omitempty"` - HostIp string `json:"HostIp,omitempty"` + DockerID string `json:"DockerId"` + DockerName string `json:"DockerName"` + Name string `json:"Name"` + Ports []tmdsresponse.PortResponse `json:"Ports,omitempty"` + Networks []tmdsresponse.Network `json:"Networks,omitempty"` + Volumes []tmdsresponse.VolumeResponse `json:"Volumes,omitempty"` } // NewTaskResponse creates a TaskResponse for a task. @@ -113,7 +97,7 @@ func NewContainerResponse(dockerContainer *apicontainer.DockerContainer, eni *ap resp.Volumes = NewVolumesResponse(dockerContainer) if eni != nil { - resp.Networks = []containermetadata.Network{ + resp.Networks = []tmdsresponse.Network{ { NetworkMode: utils.NetworkModeAWSVPC, IPv4Addresses: eni.GetIPV4Addresses(), @@ -125,9 +109,9 @@ func NewContainerResponse(dockerContainer *apicontainer.DockerContainer, eni *ap } // NewPortBindingsResponse creates PortResponse for a container. -func NewPortBindingsResponse(dockerContainer *apicontainer.DockerContainer, eni *apieni.ENI) []PortResponse { +func NewPortBindingsResponse(dockerContainer *apicontainer.DockerContainer, eni *apieni.ENI) []tmdsresponse.PortResponse { container := dockerContainer.Container - resp := []PortResponse{} + resp := []tmdsresponse.PortResponse{} bindings := container.GetKnownPortBindings() @@ -138,7 +122,7 @@ func NewPortBindingsResponse(dockerContainer *apicontainer.DockerContainer, eni } for _, binding := range bindings { - port := PortResponse{ + port := tmdsresponse.PortResponse{ ContainerPort: binding.ContainerPort, Protocol: binding.Protocol.String(), } @@ -155,14 +139,14 @@ func NewPortBindingsResponse(dockerContainer *apicontainer.DockerContainer, eni } // NewVolumesResponse creates VolumeResponse for a container -func NewVolumesResponse(dockerContainer *apicontainer.DockerContainer) []VolumeResponse { +func NewVolumesResponse(dockerContainer *apicontainer.DockerContainer) []tmdsresponse.VolumeResponse { container := dockerContainer.Container - var resp []VolumeResponse + var resp []tmdsresponse.VolumeResponse volumes := container.GetVolumes() for _, volume := range volumes { - volResp := VolumeResponse{ + volResp := tmdsresponse.VolumeResponse{ DockerName: volume.Name, Source: volume.Source, Destination: volume.Destination, diff --git a/agent/handlers/v2/response.go b/agent/handlers/v2/response.go index f80c4dc39a7..8ac65c4cd77 100644 --- a/agent/handlers/v2/response.go +++ b/agent/handlers/v2/response.go @@ -20,10 +20,10 @@ import ( "github.com/aws/amazon-ecs-agent/agent/api" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/aws/aws-sdk-go/aws" "github.com/cihub/seelog" @@ -53,27 +53,27 @@ type TaskResponse struct { // ContainerResponse defines the schema for the container response // JSON object type ContainerResponse struct { - ID string `json:"DockerId"` - Name string `json:"Name"` - DockerName string `json:"DockerName"` - Image string `json:"Image"` - ImageID string `json:"ImageID"` - Ports []v1.PortResponse `json:"Ports,omitempty"` - Labels map[string]string `json:"Labels,omitempty"` - DesiredStatus string `json:"DesiredStatus"` - KnownStatus string `json:"KnownStatus"` - ExitCode *int `json:"ExitCode,omitempty"` - Limits LimitsResponse `json:"Limits"` - CreatedAt *time.Time `json:"CreatedAt,omitempty"` - StartedAt *time.Time `json:"StartedAt,omitempty"` - FinishedAt *time.Time `json:"FinishedAt,omitempty"` - Type string `json:"Type"` - Networks []containermetadata.Network `json:"Networks,omitempty"` - Health *apicontainer.HealthStatus `json:"Health,omitempty"` - Volumes []v1.VolumeResponse `json:"Volumes,omitempty"` - LogDriver string `json:"LogDriver,omitempty"` - LogOptions map[string]string `json:"LogOptions,omitempty"` - ContainerARN string `json:"ContainerARN,omitempty"` + ID string `json:"DockerId"` + Name string `json:"Name"` + DockerName string `json:"DockerName"` + Image string `json:"Image"` + ImageID string `json:"ImageID"` + Ports []tmdsresponse.PortResponse `json:"Ports,omitempty"` + Labels map[string]string `json:"Labels,omitempty"` + DesiredStatus string `json:"DesiredStatus"` + KnownStatus string `json:"KnownStatus"` + ExitCode *int `json:"ExitCode,omitempty"` + Limits LimitsResponse `json:"Limits"` + CreatedAt *time.Time `json:"CreatedAt,omitempty"` + StartedAt *time.Time `json:"StartedAt,omitempty"` + FinishedAt *time.Time `json:"FinishedAt,omitempty"` + Type string `json:"Type"` + Networks []tmdsresponse.Network `json:"Networks,omitempty"` + Health *apicontainer.HealthStatus `json:"Health,omitempty"` + Volumes []tmdsresponse.VolumeResponse `json:"Volumes,omitempty"` + LogDriver string `json:"LogDriver,omitempty"` + LogOptions map[string]string `json:"LogOptions,omitempty"` + ContainerARN string `json:"ContainerARN,omitempty"` } // LimitsResponse defines the schema for task/cpu limits response @@ -272,7 +272,7 @@ func NewContainerResponse( } for _, binding := range container.GetKnownPortBindings() { - port := v1.PortResponse{ + port := tmdsresponse.PortResponse{ ContainerPort: binding.ContainerPort, Protocol: binding.Protocol.String(), } @@ -289,7 +289,7 @@ func NewContainerResponse( } if eni != nil { - resp.Networks = []containermetadata.Network{ + resp.Networks = []tmdsresponse.Network{ { NetworkMode: utils.NetworkModeAWSVPC, IPv4Addresses: eni.GetIPV4Addresses(), diff --git a/agent/handlers/v3/container_metadata_handler.go b/agent/handlers/v3/container_metadata_handler.go index 7860f57a199..a7c90281780 100644 --- a/agent/handlers/v3/container_metadata_handler.go +++ b/agent/handlers/v3/container_metadata_handler.go @@ -18,9 +18,9 @@ import ( "fmt" "net/http" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" "github.com/pkg/errors" @@ -78,7 +78,7 @@ func GetContainerResponse(containerID string, state dockerstate.TaskEngineState) } // GetContainerNetworkMetadata returns the network metadata for the container -func GetContainerNetworkMetadata(containerID string, state dockerstate.TaskEngineState) ([]containermetadata.Network, error) { +func GetContainerNetworkMetadata(containerID string, state dockerstate.TaskEngineState) ([]tmdsresponse.Network, error) { dockerContainer, ok := state.ContainerByID(containerID) if !ok { return nil, errors.Errorf("Unable to find container '%s'", containerID) @@ -98,17 +98,17 @@ func GetContainerNetworkMetadata(containerID string, state dockerstate.TaskEngin // Extensive Network information is not available for Docker API versions 1.17-1.20 // Instead we only get the details of the first network - networks := make([]containermetadata.Network, 0) + networks := make([]tmdsresponse.Network, 0) if len(settings.Networks) > 0 { for modeFromSettings, containerNetwork := range settings.Networks { networkMode := modeFromSettings ipv4Addresses := []string{containerNetwork.IPAddress} - network := containermetadata.Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses} + network := tmdsresponse.Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses} networks = append(networks, network) } } else { ipv4Addresses := []string{ipv4AddressFromSettings} - network := containermetadata.Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses} + network := tmdsresponse.Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses} networks = append(networks, network) } return networks, nil diff --git a/agent/handlers/v4/container_metadata_handler.go b/agent/handlers/v4/container_metadata_handler.go index 529147c52bd..39a7756f2d9 100644 --- a/agent/handlers/v4/container_metadata_handler.go +++ b/agent/handlers/v4/container_metadata_handler.go @@ -18,9 +18,9 @@ import ( "fmt" "net/http" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" v3 "github.com/aws/amazon-ecs-agent/agent/handlers/v3" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/cihub/seelog" "github.com/pkg/errors" @@ -103,12 +103,12 @@ func GetContainerNetworkMetadata(containerID string, state dockerstate.TaskEngin for modeFromSettings, containerNetwork := range settings.Networks { networkMode := modeFromSettings ipv4Addresses := []string{containerNetwork.IPAddress} - network := Network{Network: containermetadata.Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses}} + network := Network{Network: tmdsresponse.Network{NetworkMode: networkMode, IPv4Addresses: ipv4Addresses}} networks = append(networks, network) } } else { ipv4Addresses := []string{ipv4AddressFromSettings} - network := Network{Network: containermetadata.Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses}} + network := Network{Network: tmdsresponse.Network{NetworkMode: networkModeFromHostConfig, IPv4Addresses: ipv4Addresses}} networks = append(networks, network) } return networks, nil diff --git a/agent/handlers/v4/response.go b/agent/handlers/v4/response.go index e0a2d4b17a8..043e9aa445b 100644 --- a/agent/handlers/v4/response.go +++ b/agent/handlers/v4/response.go @@ -17,10 +17,10 @@ import ( "github.com/aws/amazon-ecs-agent/agent/api" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" apitask "github.com/aws/amazon-ecs-agent/agent/api/task" - "github.com/aws/amazon-ecs-agent/agent/containermetadata" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" v2 "github.com/aws/amazon-ecs-agent/agent/handlers/v2" apieni "github.com/aws/amazon-ecs-agent/ecs-agent/api/eni" + tmdsresponse "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils" "github.com/pkg/errors" ) @@ -45,7 +45,7 @@ type ContainerResponse struct { // interface(s) on top of what is supported by v4. // See `NetworkInterfaceProperties` for more details. type Network struct { - containermetadata.Network + tmdsresponse.Network // NetworkInterfaceProperties specifies additional properties of the network // of the network interface that are exposed via the metadata server. // We currently populate this only for the `awsvpc` networking mode. @@ -147,7 +147,7 @@ func NewContainerResponse( // look up the task information in the local state based on the id, which could be // either task arn or contianer id. func toV4NetworkResponse( - networks []containermetadata.Network, + networks []tmdsresponse.Network, lookup func() (*apitask.Task, bool), ) ([]Network, error) { var resp []Network diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response/response.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response/response.go new file mode 100644 index 00000000000..db94ab4cd81 --- /dev/null +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response/response.go @@ -0,0 +1,38 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// This package defines some common types used by all task metadata functions +package response + +// VolumeResponse is the schema for the volume response JSON object +type VolumeResponse struct { + DockerName string `json:"DockerName,omitempty"` + Source string `json:"Source,omitempty"` + Destination string `json:"Destination,omitempty"` +} + +// PortResponse defines the schema for portmapping response JSON +// object. +type PortResponse struct { + ContainerPort uint16 `json:"ContainerPort,omitempty"` + Protocol string `json:"Protocol,omitempty"` + HostPort uint16 `json:"HostPort,omitempty"` + HostIp string `json:"HostIp,omitempty"` +} + +// Network is a struct that keeps track of metadata of a network interface +type Network struct { + NetworkMode string `json:"NetworkMode,omitempty"` + IPv4Addresses []string `json:"IPv4Addresses,omitempty"` + IPv6Addresses []string `json:"IPv6Addresses,omitempty"` +} diff --git a/agent/vendor/modules.txt b/agent/vendor/modules.txt index 6a041f65c57..f63327fcef9 100644 --- a/agent/vendor/modules.txt +++ b/agent/vendor/modules.txt @@ -21,6 +21,7 @@ github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/mocks github.com/aws/amazon-ecs-agent/ecs-agent/logger/audit/request github.com/aws/amazon-ecs-agent/ecs-agent/logger/field github.com/aws/amazon-ecs-agent/ecs-agent/tmds +github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/response github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v1 github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v2 diff --git a/ecs-agent/tmds/handlers/response/response.go b/ecs-agent/tmds/handlers/response/response.go new file mode 100644 index 00000000000..db94ab4cd81 --- /dev/null +++ b/ecs-agent/tmds/handlers/response/response.go @@ -0,0 +1,38 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// This package defines some common types used by all task metadata functions +package response + +// VolumeResponse is the schema for the volume response JSON object +type VolumeResponse struct { + DockerName string `json:"DockerName,omitempty"` + Source string `json:"Source,omitempty"` + Destination string `json:"Destination,omitempty"` +} + +// PortResponse defines the schema for portmapping response JSON +// object. +type PortResponse struct { + ContainerPort uint16 `json:"ContainerPort,omitempty"` + Protocol string `json:"Protocol,omitempty"` + HostPort uint16 `json:"HostPort,omitempty"` + HostIp string `json:"HostIp,omitempty"` +} + +// Network is a struct that keeps track of metadata of a network interface +type Network struct { + NetworkMode string `json:"NetworkMode,omitempty"` + IPv4Addresses []string `json:"IPv4Addresses,omitempty"` + IPv6Addresses []string `json:"IPv6Addresses,omitempty"` +}