├── .github └── workflows │ └── ci.yml ├── .mergify.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── check-ecs-exec.sh ├── demo.gif └── example-result.png /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | shellcheck: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Run ShellCheck 13 | run: find . -type f -name "*.sh" -exec shellcheck {} + -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Merge on CI success and review approval 3 | conditions: 4 | - -merged 5 | - -closed 6 | - "#approved-reviews-by>=1" 7 | - "#changes-requested-reviews-by=0" 8 | actions: 9 | merge: 10 | method: squash 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | ## Reporting Bugs/Feature Requests 10 | 11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 12 | 13 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 15 | 16 | * A reproducible test case or series of steps 17 | * The version of our code being used 18 | * Any modifications you've made relevant to the bug 19 | * Anything unusual about your environment or deployment 20 | 21 | ## Contributing via Pull Requests 22 | 23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 24 | 25 | 1. You are working against the latest source on the *main* branch. 26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 28 | 29 | To send us a pull request, please: 30 | 31 | 1. Fork the repository. 32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 33 | 3. Ensure local tests pass. 34 | 4. Commit to your fork using clear commit messages. 35 | 5. Send us a pull request, answering any default questions in the pull request interface. 36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 37 | 38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | ## Finding contributions to work on 42 | 43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 44 | 45 | ## Code of Conduct 46 | 47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 49 | opensource-codeofconduct@amazon.com with any additional questions or comments. 50 | 51 | ## Security issue notifications 52 | 53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 54 | 55 | ## Licensing 56 | 57 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon ECS Exec Checker 2 | 3 | The `check-ecs-exec.sh` script allows you to check and validate both your CLI environment and ECS cluster/task are ready for `ECS Exec`, by calling [various AWS APIs](check-ecs-exec.sh#L21) on behalf of you. You can learn more about ECS Exec on [the containers blog post](https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/) and [the official developer guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html). 4 | 5 | ![](demo.gif) 6 | 7 | ## Prerequisites 8 | 9 | The `check-ecs-exec.sh` requires the following commands. 10 | 11 | - jq 12 | - AWS CLI v1.19.28/v2.1.30 or later 13 | 14 | ## Usage 15 | 16 | ```shell 17 | $ ./check-ecs-exec.sh 18 | ``` 19 | 20 | _Example 1 - Run without cloning Git repo_ 21 | 22 | The `check-ecs-exec.sh` will use your `default` AWS CLI profile and the AWS region. 23 | 24 | ```shell 25 | $ bash <( curl -Ls https://raw.githubusercontent.com/aws-containers/amazon-ecs-exec-checker/main/check-ecs-exec.sh ) 26 | ``` 27 | 28 | _Example 2 - With AWS\_* variables_ 29 | 30 | The `check-ecs-exec.sh` will use the `myprofile` AWS CLI profile and `us-west-2` AWS region. 31 | 32 | ```shell 33 | $ export AWS_PROFILE=myprofile 34 | $ export AWS_REGION=us-west-2 35 | 36 | $ bash <( curl -Ls https://raw.githubusercontent.com/aws-containers/amazon-ecs-exec-checker/main/check-ecs-exec.sh ) 37 | ``` 38 | 39 | _Example 3 - With MFA_ 40 | 41 | The `check-ecs-exec.sh` automatically detects your MFA configuration for the AWS CLI. But you can also explicitly specify which MFA device to use by setting the ARN of the MFA device to `AWS_MFA_SERIAL` environment variable. 42 | 43 | _Example 4 - Switch AWS CLI binaries_ 44 | 45 | If you have multiple AWS CLI installations in your environment, both AWS CLI v1 and v2 for example, you can choose which AWS CLI binary to use by passing the `AWS_CLI_BIN` env variable. 46 | 47 | ```shell 48 | $ AWS_CLI_BIN=aws-v1 ./check-ecs-exec.sh 49 | ``` 50 | 51 | ## Checks 52 | 53 | The `check-ecs-exec.sh` shows the results with three text colors, 🟢(Green), 🟡(Yellow), and 🔴(Red). Each color tells how you'll handle the results. 54 | 55 | 1. 🟢(Green) - The configuration or the status is okay. 56 | 2. 🟡(Yellow) - The configuration or the status should or would be recommended to fix, but you can use ECS Exec without fixing them. 57 | 3. 🔴(Red) - You need to fix those results before using ECS Exec. 58 | 59 | In the following screenshot for instance, we need to install the Session Manager plugin and give required permissions to the task role at least, but we can ignore the audit-logging configuration. 60 | 61 | Note that it shows "SSM PrivateLink" at the bottom as a 🟡(yellow) result, but it can be a 🔴(red) result if your ECS task doesn't have proper outbound internet connectivity. In this case, you will need to configure an [SSM PrivateLink](https://docs.aws.amazon.com/vpc/latest/privatelink/vpce-interface.html#create-interface-endpoint) in your VPC. 62 | 63 | [![example-result](example-result.png)](example-result.png) 64 | 65 | ## Reference - How to handle 🔴(Red) and 🟡(Yellow) items 66 | 67 | 1. **_🔴 Pre-flight check failed: `jq` command is missing_** 68 | Install the `jq` command. See [the official documentation](https://stedolan.github.io/jq/download/) for the details and how to install. 69 | 70 | 2. **_🔴 Pre-flight check failed: `aws` command is missing_** 71 | Install the latest AWS CLI. See [the official documentation for the AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) or [the official documentation for the AWS CLI v1](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html) for the details and how to install. 72 | 73 | 3. **_🔴 Pre-flight check failed: ECS Exec requires the AWS CLI v1.19.28/v2.1.30 or later_** 74 | Upgrade to the latest AWS CLI. See [the official documentation for the AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) or [the official documentation for the AWS CLI v1](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html) for the details and how to upgrade. 75 | 76 | 4. **_🔴 Session Manager Plugin | Missing_** 77 | Install the Session Manager plugin. See [the official documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) for the details and how to install. 78 | 79 | 4. **_🟡 Cluster Configuration | Audit Logging Not Configured / Disabled_** 80 | This check item won't block you to use ECS Exec, but we recommend you to enable logging and auditing for your ECS cluster from the security perspective. See [the official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-logging) for the details and how to enable them. 81 | 82 | 5. **_🔴 Can I ExecuteCommand? | ecs:ExecuteCommand: implicitDeny_** 83 | The IAM user/role you used for the `check-ecs-exec.sh` are not allowed to use the `ecs:ExecuteCommand` API. See the "[Using IAM policies to limit access to ECS Exec](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-best-practices-limit-access-execute-command) section in the official documentation to add the required permission to the IAM user/role. 84 | Note that the `Condition` element of the IAM policy is not currently supported to evaluate by `check-ecs-exec.sh`. 85 | 86 | 6. **_🔴 Can I ExecuteCommand? | kms:GenerateDataKey: implicitDeny_** 87 | The IAM user/role you used for the `check-ecs-exec.sh` are not allowed to use the `kms:GenerateDataKey` API with the given KMS Key ID which you're using for the logging and auditing configuration for ECS exec. See the "[IAM permissions required for encryption using your own KMS customer master key (CMK)](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-logging) section under the "Logging and Auditing using ECS Exec" section in the official documentation to add the required permission to the IAM user/role. 88 | Note that the `Condition` element of the IAM policy is not currently supported to evaluate by `check-ecs-exec.sh`. 89 | 90 | 7. **_🟡 Can I ExecuteCommand? | ssm:StartSession denied?: allowed_** 91 | The result means your IAM user/role is allowed to do `ssm:StartSession` action to the ECS task. This check item won't block you to use ECS Exec, but we recommend you to limit access to the `ssm:StartSession` API, from the security and the principle of least privilege perspectives. See [the ECS official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-limit-access-start-session) for further details. 92 | Note that the `Condition` element of the IAM policy is not currently supported to evaluate by `check-ecs-exec.sh`. 93 | 94 | 8. **_🔴 Task Status | DEACTIVATING or STOPPING or DEPROVISIONING or STOPPED_** 95 | Your ECS task has already stopped, or is shutting down. ECS Exec requires the task is in the `RUNNING` state. Restart your ECS task if it's a standalone task, or wait for another task if it's a part of an ECS service. See also [the Task lifecycle](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-lifecycle.html) in the ECS documentation for more details. 96 | 97 | 9. **_🟡 Task Status | PROVISIONING or ACTIVATING or PENDING_** 98 | Your ECS task is in the middle of its starting process. ECS Exec requires the task is in the `RUNNING` state. Wait few more seconds for the task to be ready. See also [the Task lifecycle](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-lifecycle.html) in the ECS documentation for more details. 99 | 100 | 10. **_🔴 Platform Version | 1.3.0 (Required: >= 1.4.0)_** 101 | On AWS Fargate, `ECS Exec` requires the Platform version 1.4.0 or higher (Linux) or 1.0.0 (Windows). If your ECS task is part of an ECS service, then you can update the platform version by specifying the `PlatformVersion` parameter for the `UpdateService` API. If your ECS task is a standalone task, then you need to re-run the ECS task with the `PlatformVersion` parameter specified for the `RunTask` API. See also [the migration guide from the previous PVs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/platform_versions.html#platform-version-migration). 102 | 103 | 11. **_🔴 ECS Agent Version | x.y.z (Required: >= 1.50.2)_** 104 | You need to update the version of the ECS Container Agent for your EC2 instance where your ECS task runs. See [the ECS official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-update.html) for the details and how to update. 105 | 106 | 12. **_🔴 Exec Enabled for Task | NO_** 107 | You need to enable the ECS Exec feature for your ECS service or your ECS standalone task. If your ECS task is part of an ECS service, then you can update the ECS by specifying the `EnableExecuteCommand` parameter for the `UpdateService` API. If your ECS task is a standalone task, then you need to re-run the ECS task with the `EnableExecuteCommand` parameter specified for the `RunTask` API. 108 | 109 | 13. **_🔴 Managed Agent Status | STOPPED (Reason: stopped-reason-here)_** 110 | The managed agent for a container in your Task has stopped for some reasons. If you see this error again and again even after re-running your ECS task, then make sure you have other results from `check-ecs-exec.sh` are all green. 111 | 112 | 14. **_🟡 Init Process Enabled | Disabled_** 113 | This check item won't block you to use ECS Exec, but we recommend you to add the `initProcessEnabled` flag to your ECS task definition for each container to avoid having orphaned and zombie processes. See the "Considerations for using ECS Exec" in [the ECS official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-considerations) for more details. 114 | 115 | 15. **_🔴 Read-Only Root Filesystem | ReadOnly_** 116 | ECS Exec uses the SSM agent as its managed agent, and the agents requires that the container file system is able to be written in order to create the required directories and files. Therefore, you need to set the `readonlyRootFilesystem` flag as `false` in your task definition to exec into the container using ECS Exec. See the "Considerations for using ECS Exec" in [the ECS official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-considerations) for more details. 117 | 118 | 16. **_🔴 EC2 or Task Role | Not Configured"_ or _{serviceName}:{ActionName}: implicitDeny_** 119 | Your ECS task needs a task role or an instance role of the underlying EC2 instance with some permissions for using SSM Session Manager at least. See the [IAM permissions required for ECS Exec](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-enabling-and-using) section and the [Enabling logging and auditing in your tasks and services](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-logging) section in the official documentation for the details. 120 | Note that the `Condition` element of the IAM policy is not currently supported to evaluate by `check-ecs-exec.sh`. 121 | 122 | 17. **_🟡 SSM PrivateLink "com.amazonaws.(region).ssmmessages" not found_** 123 | The `check-ecs-exec.sh` found one or more VPC endpoints configured in the VPC for your task, so you **may** want to add an additional SSM PrivateLink for your VPC. Make sure your ECS task has proper outbound internet connectivity, and if it doesn't, then you **need** to configure an additional SSM PrivateLink for your VPC. 124 | 125 | 18. **_🔴 VPC Endpoints | CHECK FAILED_** 126 | The `check-ecs-exec.sh` doesn't support checking this item for shared VPC subnets using [AWS Resouce Access Manager (AWS RAM)](https://aws.amazon.com/ram/). In short, this may not an issue to use ECS Exec if your ECS task VPC doesn't have any VPC endpoint and the task has proper outbound internet connectivity. Make sure to consult your administrator with the official ECS Exec documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-considerations) to find if your VPC need to have an additional VPC endpoint. 127 | 128 | 19. **🟡 Environment Variables : defined** 129 | SSM uses the AWS SDK which uses the [default chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) when determining authentication. This means if AWS_ACCESS_KEY, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY are defined in the environment variables and the permissions there do not provide the required permissions for SSM to work, then the execute-command will fail. It is recomended not to define these environment variables. 130 | 131 | 20. **🟡 PidMode : task** 132 | If you are [sharing a PID namespace in a task](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#other_task_definition_params), you can only start ECS Exec sessions into one container. See the "Considerations for using ECS Exec" in [the ECS official documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-considerations) for more details. 133 | 134 | ## Security 135 | 136 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 137 | 138 | ## License 139 | 140 | Licensed under the MIT-0 License. See the [LICENSE](LICENSE) file. 141 | -------------------------------------------------------------------------------- /check-ecs-exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | # shellcheck disable=SC2059 7 | 8 | CHECKER_VERSION=v0.7 9 | 10 | # Script Name: check-ecs-exec.sh 11 | # Usage : bash ./check-ecs-exec.sh 12 | 13 | set -euo pipefail 14 | 15 | ## NOTE: Checks in this script are mainly based on: 16 | ## 17 | ## "Using Amazon ECS Exec for debugging - Amazon Elastic Container Service" 18 | ## https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html 19 | ## 20 | ## "NEW – Using Amazon ECS Exec to access your containers on AWS Fargate and Amazon EC2" 21 | ## https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/ 22 | ## 23 | 24 | ## NOTE: This script at least needs the following permissions. 25 | ## 1. If you use an IAM user with an assumed role to run the script, 26 | ## then you need to allow the "iam:ListRoles" action in addition to the following. 27 | ## 2. If you configured your ECS cluster to use KMS key for ECS Exec, 28 | ## then you need to allow the "kms:DescribeKey" action in addition to the following. 29 | ## { 30 | ## "Version": "2012-10-17", 31 | ## "Statement": [ 32 | ## { 33 | ## "Effect": "Allow", 34 | ## "Action": [ 35 | ## "iam:GetInstanceProfile", 36 | ## "iam:SimulatePrincipalPolicy", 37 | ## "ec2:DescribeSubnets", 38 | ## "ec2:DescribeVpcEndpoints", 39 | ## "ecs:DescribeClusters", 40 | ## "ecs:DescribeContainerInstances", 41 | ## "ecs:DescribeTaskDefinition", 42 | ## "ecs:DescribeTasks" 43 | ## ], 44 | ## "Resource": "*" 45 | ## } 46 | ## ] 47 | ## } 48 | 49 | # If you have multiple AWS CLI binaries, v1 and v2 for instance, you can choose which AWS CLI binary to use by setting the AWS_CLI_BIN env var. 50 | # e.g. AWS_CLI_BIN=aws-v1 ./check-ecs-exec.sh YOUR_ECS_CLUSTER_NAME YOUR_ECS_TASK_ID 51 | AWS_CLI_BIN=${AWS_CLI_BIN:-aws} 52 | 53 | # Force AWS CLI output format to json to use jq to parse its output 54 | export AWS_DEFAULT_OUTPUT=json 55 | 56 | # Colors for output 57 | COLOR_DEFAULT='\033[0m' 58 | COLOR_RED='\033[0;31m' 59 | COLOR_YELLOW='\033[1;33m' 60 | COLOR_GREEN='\033[0;32m' 61 | 62 | # Validation for required parameters 63 | CLUSTER_NAME=${1:-None} # A cluster name or a full ARN of the cluster 64 | TASK_ID=${2:-None} # A task ID or a full ARN of the task 65 | if [[ "${CLUSTER_NAME}" = "None" || "${TASK_ID}" = "None" ]]; then 66 | printf "${COLOR_RED}Usage:\n" >&2 67 | printf " ./check-ecs-exec.sh YOUR_ECS_CLUSTER_NAME YOUR_ECS_TASK_ID${COLOR_DEFAULT}\n" >&2 68 | exit 1 69 | fi 70 | 71 | #### Functions 72 | printSectionHeaderLine() { 73 | printf "${COLOR_DEFAULT}-------------------------------------------------------------\n" 74 | } 75 | equalsOrGreaterVersion() { 76 | required=$1 77 | current=$2 78 | if [[ "$(printf '%s\n' "$required" "$current" | sort -V | head -n1)" = "$required" ]]; then 79 | return 80 | fi 81 | false 82 | } 83 | getRoleArnForAssumedRole() { 84 | callerIdentityJson=$1 85 | ROLE_ID=$(echo "${callerIdentityJson}" | jq -r ".UserId" | cut -d: -f1) 86 | aws iam list-roles --query "Roles[?RoleId=='${ROLE_ID}'].Arn" --output text 87 | } 88 | # For `iam simulate-principal-policy` 89 | readEvalDecision() { 90 | evalResultsJson=$1 91 | actionName=$2 92 | echo "${evalResultsJson}" | jq -r --arg ACTION_NAME "$actionName" '.EvaluationResults[] | select(.EvalActionName==$ACTION_NAME) | .EvalDecision' 93 | } 94 | showEvalResult() { 95 | evalResult=$1 96 | actionName=$2 97 | printf "${COLOR_DEFAULT} ${actionName}: " 98 | if [[ "${evalResult}" = "allowed" ]]; then 99 | printf "${COLOR_GREEN}${evalResult}\n" 100 | else 101 | printf "${COLOR_RED}${evalResult}\n" 102 | fi 103 | } 104 | 105 | ## 1. CHECK PREREQUISITES FOR check-ecs-exec.sh ########################################## 106 | printSectionHeaderLine 107 | printf "${COLOR_DEFAULT}Prerequisites for check-ecs-exec.sh ${CHECKER_VERSION}\n" 108 | printSectionHeaderLine 109 | ########################################################################################## 110 | 111 | # Check if jq command exists 112 | command -v jq >/dev/null 2>&1 && status="$?" || status="$?" 113 | if [[ ! "${status}" = 0 ]]; then 114 | printf "${COLOR_RED}Pre-flight check failed: \`jq\` command is missing${COLOR_DEFAULT}\n" >&2 115 | exit 1 116 | fi 117 | printf "${COLOR_DEFAULT} jq | ${COLOR_GREEN}OK ${COLOR_DEFAULT}($(which jq))\n" 118 | 119 | # Check if aws command exists 120 | command -v "${AWS_CLI_BIN}" >/dev/null 2>&1 && status="$?" || status="$?" 121 | if [[ ! "${status}" = 0 ]]; then 122 | printf "${COLOR_RED}Pre-flight check failed: \`${AWS_CLI_BIN}\` command is missing${COLOR_DEFAULT}\n" >&2 123 | exit 1 124 | fi 125 | printf "${COLOR_DEFAULT} AWS CLI | ${COLOR_GREEN}OK ${COLOR_DEFAULT}($(which "${AWS_CLI_BIN}"))\n" 126 | 127 | # Find AWS region 128 | REGION=$(${AWS_CLI_BIN} configure get region | tr -d "\r" || echo "") 129 | export AWS_REGION=${AWS_REGION:-$REGION} 130 | # Check region configuration in "source_profile" if the user uses MFA configurations 131 | source_profile=$(${AWS_CLI_BIN} configure get source_profile || echo "") 132 | if [ "${AWS_REGION}" = "" ] && [ "${source_profile}" != "" ]; then 133 | region=$(${AWS_CLI_BIN} configure get region --profile "${source_profile}" || echo "") 134 | export AWS_REGION="${region}" 135 | fi 136 | if [[ "${AWS_REGION}" = "" ]]; then 137 | printf "${COLOR_RED}Pre-flight check failed: Missing AWS region. Use the \`aws configure set default.region\` command or set the \"AWS_REGION\" environment variable.${COLOR_DEFAULT}\n" >&2 138 | exit 1 139 | fi 140 | 141 | ## 2. CHECK PREREQUISITES FOR USING ECS EXEC FEATURE VIA AWS CLI ######################### 142 | printf "\n" 143 | printSectionHeaderLine 144 | printf "${COLOR_DEFAULT}Prerequisites for the AWS CLI to use ECS Exec\n" 145 | printSectionHeaderLine 146 | ########################################################################################## 147 | 148 | # MFA 149 | AWS_MFA_SERIAL=${AWS_MFA_SERIAL:-$(${AWS_CLI_BIN} configure get mfa_serial || echo "")} 150 | ROLE_TO_BE_ASSUMED=$(${AWS_CLI_BIN} configure get role_arn || echo "") 151 | SOURCE_PROFILE=$(${AWS_CLI_BIN} configure get source_profile || echo "") 152 | # Normally we don't need to ask MFA code thanks to the AWS CLI 153 | # but we do need to prompt explicitly if the "AWS_MFA_SERIAL" value only exists without "role_arn" and "source_profile" 154 | if [ "${AWS_MFA_SERIAL}" != "" ] && [ "${ROLE_TO_BE_ASSUMED}" == "" ] && [ "${SOURCE_PROFILE}" == "" ]; then 155 | # Prpmpt users to enter MFA code to obtain temporary credentials 156 | mfa_code="" 157 | while true; do 158 | printf "\n" 159 | printf "Type MFA code for ${AWS_MFA_SERIAL}: " 160 | read -rs mfa_code 161 | if [ -z "${mfa_code}" ]; then 162 | printf "${COLOR_RED}MFA code cannot be empty${COLOR_DEFAULT}" 163 | continue 164 | fi 165 | break 166 | done 167 | 168 | tmpCreds=$(${AWS_CLI_BIN} sts get-session-token --serial-number "${AWS_MFA_SERIAL}" --token-code "${mfa_code}") 169 | accessKey=$( echo "${tmpCreds}" | jq -r .Credentials.AccessKeyId ) 170 | secretKey=$( echo "${tmpCreds}" | jq -r .Credentials.SecretAccessKey ) 171 | sessionToken=$( echo "${tmpCreds}" | jq -r .Credentials.SessionToken ) 172 | export AWS_ACCESS_KEY_ID="${accessKey}" 173 | export AWS_SECRET_ACCESS_KEY="${secretKey}" 174 | export AWS_SESSION_TOKEN="${sessionToken}" 175 | fi 176 | 177 | # Find caller identity 178 | callerIdentityJson=$(${AWS_CLI_BIN} sts get-caller-identity) 179 | ACCOUNT_ID=$(echo "${callerIdentityJson}" | jq -r ".Account") 180 | CALLER_IAM_ARN=$(echo "${callerIdentityJson}" | jq -r ".Arn") 181 | case "${CALLER_IAM_ARN}" in 182 | *:user/*|*:role/*|*:group/* ) MY_IAM_ARN="${CALLER_IAM_ARN}";; 183 | *:assumed-role/*) MY_IAM_ARN=$(getRoleArnForAssumedRole "${callerIdentityJson}");; 184 | * ) printf "${COLOR_RED}Pre-flight check failed: The ARN \"${CALLER_IAM_ARN}\" associated with the caller(=you) is not supported. Try again either with one of an IAM user, an IAM role, or an assumed IAM role.${COLOR_DEFAULT}\n" >&2 && exit 1;; 185 | esac 186 | if [[ "${MY_IAM_ARN}" = "" ]]; then 187 | printf "${COLOR_RED}Unknown error: Failed to get the role ARN of the caller(=you).${COLOR_DEFAULT}\n" >&2 188 | exit 1 189 | fi 190 | 191 | # Check task existence 192 | describedTaskJson=$(${AWS_CLI_BIN} ecs describe-tasks \ 193 | --cluster "${CLUSTER_NAME}" \ 194 | --tasks "${TASK_ID}" \ 195 | --output json) 196 | existTask=$(echo "${describedTaskJson}" | jq -r ".tasks[0].taskDefinitionArn") 197 | if [[ "${existTask}" = "null" ]]; then 198 | printf "${COLOR_RED}Pre-flight check failed: The specified ECS task does not exist.\n\ 199 | Make sure the parameters you have specified for cluster \"${CLUSTER_NAME}\" and task \"${TASK_ID}\" are both valid.${COLOR_DEFAULT}\n" 200 | exit 1 201 | fi 202 | 203 | # Check whether the AWS CLI v1.19.28/v2.1.30 or later exists 204 | executeCommandEnabled=$(echo "${describedTaskJson}" | jq -r ".tasks[0].enableExecuteCommand") 205 | if [[ "${executeCommandEnabled}" = "null" ]]; then 206 | printf "${COLOR_RED}Pre-flight check failed: ECS Exec requires the AWS CLI v1.19.28/v2.1.30 or later.\n\ 207 | Please update the AWS CLI and try again?\n\ 208 | For v2: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html\n\ 209 | For v1: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html${COLOR_DEFAULT}\n" 210 | exit 1 211 | fi 212 | awsCliVersion=$(${AWS_CLI_BIN} --version 2>&1 | tr -d "\r") 213 | printf "${COLOR_DEFAULT} AWS CLI Version | ${COLOR_GREEN}OK ${COLOR_DEFAULT}(${awsCliVersion})\n" 214 | 215 | # Check whether the Session Manager plugin exists 216 | printf "${COLOR_DEFAULT} Session Manager Plugin | " 217 | command -v session-manager-plugin >/dev/null 2>&1 && status="$?" || status="$?" 218 | if [[ "${status}" = 0 ]]; then 219 | smpVersion=$(session-manager-plugin --version) 220 | printf "${COLOR_GREEN}OK ${COLOR_DEFAULT}(${smpVersion})\n" 221 | else 222 | # https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html 223 | printf "${COLOR_RED}Missing\n" 224 | fi 225 | 226 | ## 3. CHECK CLUSTER AND TASK CONFIGURATIONS ############################################## 227 | printf "\n" 228 | printSectionHeaderLine 229 | printf "${COLOR_DEFAULT}Checks on ECS task and other resources\n" 230 | printSectionHeaderLine 231 | printf "${COLOR_DEFAULT}Region : ${AWS_REGION}\n" 232 | printf "${COLOR_DEFAULT}Cluster: ${CLUSTER_NAME}\n" 233 | printf "${COLOR_DEFAULT}Task : ${TASK_ID}\n" 234 | printSectionHeaderLine 235 | ########################################################################################## 236 | 237 | # 1. Checks on the cluster configurations (yellow) 238 | describedClusterJson=$(${AWS_CLI_BIN} ecs describe-clusters \ 239 | --clusters "${CLUSTER_NAME}" \ 240 | --include CONFIGURATIONS \ 241 | --output json) 242 | executeCommandConfigurationJson=$(echo "${describedClusterJson}" \ 243 | | jq ".clusters[0].configuration.executeCommandConfiguration") 244 | 245 | printf "${COLOR_DEFAULT} Cluster Configuration |" 246 | 247 | kmsKeyId="null" 248 | kmsKeyArn="null" 249 | logging="null" 250 | s3BucketName="null" 251 | s3KeyPrefix="null" 252 | s3Encryption="null" 253 | cloudWatchLogGroupName="null" 254 | cloudWatchLogEncryptionEnabled="null" 255 | if [[ "${executeCommandConfigurationJson}" = "null" ]]; then 256 | printf "${COLOR_YELLOW} Audit Logging Not Configured" 257 | else 258 | printf "\n" 259 | 260 | kmsKeyId=$(echo "${executeCommandConfigurationJson}" | jq -r ".kmsKeyId") 261 | printf "${COLOR_DEFAULT} KMS Key : " 262 | if [[ "${kmsKeyId}" = "null" ]]; then 263 | printf "${COLOR_YELLOW}Not Configured" 264 | else 265 | printf "${kmsKeyId}" 266 | kmsKeyArn=$(${AWS_CLI_BIN} kms describe-key --key-id "${kmsKeyId}" --query 'KeyMetadata.Arn' --output text) 267 | fi 268 | printf "\n" 269 | 270 | logging=$(echo "${executeCommandConfigurationJson}" | jq -r ".logging") 271 | printf "${COLOR_DEFAULT} Audit Logging : " 272 | if [[ "${logging}" = "null" ]]; then 273 | printf "${COLOR_YELLOW}Not Configured" 274 | elif [[ "${logging}" = "NONE" ]]; then 275 | printf "${COLOR_YELLOW}Disabled" 276 | else 277 | printf "${logging}" 278 | fi 279 | printf "\n" 280 | 281 | s3BucketName=$(echo "${executeCommandConfigurationJson}" | jq -r ".logConfiguration.s3BucketName") 282 | s3KeyPrefix=$(echo "${executeCommandConfigurationJson}" | jq -r ".logConfiguration.s3KeyPrefix") 283 | s3Encryption=$(echo "${executeCommandConfigurationJson}" | jq -r ".logConfiguration.s3EncryptionEnabled") 284 | printf "${COLOR_DEFAULT} S3 Bucket Name: " 285 | if [[ "${s3BucketName}" = "null" ]]; then 286 | printf "Not Configured" 287 | else 288 | printf "${s3BucketName}" 289 | if [[ ! "${s3KeyPrefix}" = "null" ]]; then 290 | printf ", Key Prefix: ${s3KeyPrefix}" 291 | fi 292 | printf ", Encryption Enabled: ${s3Encryption}" 293 | fi 294 | printf "\n" 295 | 296 | cloudWatchLogGroupName=$(echo "${executeCommandConfigurationJson}" | jq -r ".logConfiguration.cloudWatchLogGroupName") 297 | cloudWatchLogEncryptionEnabled=$(echo "${executeCommandConfigurationJson}" | jq -r ".logConfiguration.cloudWatchEncryptionEnabled") 298 | printf "${COLOR_DEFAULT} CW Log Group : " 299 | if [[ "${cloudWatchLogGroupName}" = "null" ]]; then 300 | printf "Not Configured" 301 | else 302 | printf "${cloudWatchLogGroupName}" 303 | printf ", Encryption Enabled: ${cloudWatchLogEncryptionEnabled}" 304 | fi 305 | fi 306 | printf "\n" 307 | 308 | # 2. Check whether "I" can call ecs:ExecuteCommand 309 | printf "${COLOR_DEFAULT} Can I ExecuteCommand? | ${MY_IAM_ARN}\n" 310 | ecsExecuteCommand="ecs:ExecuteCommand" 311 | ecsExecEvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 312 | --policy-source-arn "${MY_IAM_ARN}" \ 313 | --action-names "${ecsExecuteCommand}" \ 314 | --resource-arns "arn:aws:ecs:${AWS_REGION}:${ACCOUNT_ID}:task/${CLUSTER_NAME}/${TASK_ID}" \ 315 | --output json \ 316 | | jq -r ".EvaluationResults[0].EvalDecision") 317 | showEvalResult "${ecsExecEvalResult}" "${ecsExecuteCommand}" 318 | if [[ ! "${kmsKeyId}" = "null" ]]; then 319 | kmsGenerateDataKey="kms:GenerateDataKey" 320 | kmsGenerateDataKeyResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 321 | --policy-source-arn "${MY_IAM_ARN}" \ 322 | --action-names "${kmsGenerateDataKey}" \ 323 | --resource-arns "${kmsKeyArn}" \ 324 | --output json \ 325 | | jq -r ".EvaluationResults[0].EvalDecision") 326 | showEvalResult "${kmsGenerateDataKeyResult}" "${kmsGenerateDataKey}" 327 | fi 328 | ## Check for ensuring "I cannot" call ssm:StartSession (yellow) 329 | ### See the "Limiting access to the Start Session action" section at https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-limit-access-start-session 330 | ssmStartSession="ssm:StartSession" 331 | printf "${COLOR_DEFAULT} ${ssmStartSession} denied?: " 332 | ssmSessionEvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 333 | --policy-source-arn "${MY_IAM_ARN}" \ 334 | --action-names "${ssmStartSession}" \ 335 | --resource-arns "arn:aws:ecs:${AWS_REGION}:${ACCOUNT_ID}:task/${CLUSTER_NAME}/${TASK_ID}" \ 336 | --output json \ 337 | | jq -r ".EvaluationResults[0].EvalDecision") 338 | if [[ "${ssmSessionEvalResult}" = "allowed" ]]; then 339 | printf "${COLOR_YELLOW}" 340 | else 341 | printf "${COLOR_GREEN}" 342 | fi 343 | printf "${ssmSessionEvalResult}\n" 344 | 345 | # 3. Check the task is in RUNNING state 346 | printf "${COLOR_DEFAULT} Task Status | " 347 | taskStatus=$(echo "${describedTaskJson}" | jq -r ".tasks[0].lastStatus") 348 | stoppedReason=$(echo "${describedTaskJson}" | jq -r ".tasks[0].stoppedReason") 349 | case "${taskStatus}" in 350 | RUNNING ) printf "${COLOR_GREEN}${taskStatus}";; 351 | PROVISIONING|ACTIVATING|PENDING ) printf "${COLOR_YELLOW}${taskStatus}";; 352 | DEACTIVATING|STOPPING|DEPROVISIONING ) printf "${COLOR_RED}${taskStatus}";; 353 | STOPPED ) printf "${COLOR_RED}${taskStatus} (${stoppedReason})";; 354 | * ) printf "${COLOR_RED}${taskStatus}";; 355 | esac 356 | printf "${COLOR_DEFAULT}\n" 357 | 358 | # 4. Check the launch type, platform version, ecs-agent version 359 | launchType=$(echo "${describedTaskJson}" | jq -r ".tasks[0].launchType") 360 | describedContainerInstanceJson="" 361 | printf "${COLOR_DEFAULT} Launch Type | " 362 | if [[ "${launchType}" = "FARGATE" ]]; then # For FARGATE Launch Type 363 | printf "${COLOR_GREEN}Fargate\n" 364 | # Check the PV 365 | printf "${COLOR_DEFAULT} Platform Version | " 366 | 367 | # Detect platform family to use correct platform version required 368 | pf=$(echo "${describedTaskJson}" | jq -r ".tasks[0].platformFamily") 369 | if [[ ${pf} == *"Windows"* ]]; then 370 | requiredPV="1.0.0" #1.0.0 minimum for windows 371 | else 372 | requiredPV="1.4.0" #1.4.0 for others 373 | fi 374 | 375 | pv=$(echo "${describedTaskJson}" | jq -r ".tasks[0].platformVersion") 376 | if equalsOrGreaterVersion "${requiredPV}" "${pv}"; then 377 | printf "${COLOR_GREEN}${pv}" 378 | else 379 | printf "${COLOR_RED}${pv} (Required: >= ${requiredPV})" 380 | fi 381 | printf "\n" 382 | elif [[ "${launchType}" = "EC2" ]]; then # For EC2 Launch Type 383 | printf "${COLOR_GREEN}EC2\n" 384 | # Check the ECS-Agent version 385 | containerInstanceArn=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containerInstanceArn") 386 | requiredAgentVersion="1.50.2" 387 | describedContainerInstanceJson=$(${AWS_CLI_BIN} ecs describe-container-instances \ 388 | --cluster "${CLUSTER_NAME}" \ 389 | --container-instance "${containerInstanceArn}" \ 390 | --output json) 391 | agentVersion=$(echo "${describedContainerInstanceJson}" | jq -r ".containerInstances[0].versionInfo.agentVersion") 392 | printf "${COLOR_DEFAULT} ECS Agent Version | " 393 | if equalsOrGreaterVersion "${requiredAgentVersion}" "${agentVersion}"; then 394 | printf "${COLOR_GREEN}${agentVersion}" 395 | else 396 | printf "${COLOR_RED}${agentVersion} (Required: >= ${requiredAgentVersion})" 397 | fi 398 | printf "\n" 399 | else 400 | printf "${COLOR_YELLOW}UNKNOWN\n" 401 | fi 402 | 403 | # 5. Check whether the `execute-command` option is enabled for the task 404 | printf "${COLOR_DEFAULT} Exec Enabled for Task | " 405 | if [[ "${executeCommandEnabled}" = "true" ]]; then 406 | printf "${COLOR_GREEN}OK" 407 | else 408 | printf "${COLOR_RED}NO" 409 | fi 410 | printf "${COLOR_DEFAULT}\n" 411 | 412 | # 6. Check the managed agents' status 413 | printf "${COLOR_DEFAULT} Container-Level Checks | \n" 414 | printf "${COLOR_DEFAULT} ----------\n" 415 | printf "${COLOR_DEFAULT} Managed Agent Status" 416 | if [[ "${executeCommandEnabled}" = "false" ]]; then 417 | printf " - ${COLOR_YELLOW}SKIPPED\n" 418 | printf "${COLOR_DEFAULT} ----------\n" 419 | else 420 | printf "\n" 421 | printf "${COLOR_DEFAULT} ----------\n" 422 | agentsStatus=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containers[].managedAgents[].lastStatus") 423 | idx=0 424 | for _ in $agentsStatus; do 425 | containerName=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containers[${idx}].name") 426 | status=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containers[${idx}].managedAgents[0].lastStatus") 427 | reason=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containers[${idx}].managedAgents[0].reason") 428 | lastStartedAt=$(echo "${describedTaskJson}" | jq -r ".tasks[0].containers[${idx}].managedAgents[0].lastStartedAt") 429 | printf " $((idx+1)). " 430 | case "${status}" in 431 | *STOPPED* ) printf "${COLOR_RED}STOPPED (Reason: ${reason})";; 432 | *PENDING* ) printf "${COLOR_YELLOW}PENDING";; 433 | * ) printf "${COLOR_GREEN}RUNNING";; 434 | esac 435 | printf "${COLOR_DEFAULT} for \"${containerName}\"" 436 | if [[ "${status}" = "STOPPED" ]]; then 437 | printf " - LastStartedAt: ${lastStartedAt}" 438 | fi 439 | printf "\n" 440 | idx=$((idx+1)) 441 | done 442 | fi 443 | 444 | # 7. Check the "initProcessEnabled" flag added in the task definition (yellow) 445 | taskDefArn=$(echo "${describedTaskJson}" | jq -r ".tasks[0].taskDefinitionArn") 446 | taskDefJson=$(${AWS_CLI_BIN} ecs describe-task-definition \ 447 | --task-definition "${taskDefArn}" \ 448 | --output json) 449 | taskDefFamily=$(echo "${taskDefJson}" | jq -r ".taskDefinition.family") 450 | taskDefRevision=$(echo "${taskDefJson}" | jq -r ".taskDefinition.revision") 451 | initEnabledList=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[].linuxParameters.initProcessEnabled") 452 | idx=0 453 | printf "${COLOR_DEFAULT} ----------\n" 454 | printf "${COLOR_DEFAULT} Init Process Enabled (${taskDefFamily}:${taskDefRevision})\n" 455 | printf "${COLOR_DEFAULT} ----------\n" 456 | for enabled in $initEnabledList; do 457 | containerName=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[${idx}].name") 458 | printf " $((idx+1)). " 459 | case "${enabled}" in 460 | *true* ) printf "${COLOR_GREEN}Enabled";; 461 | *false* ) printf "${COLOR_YELLOW}Disabled";; 462 | * ) printf "${COLOR_YELLOW}Disabled";; 463 | esac 464 | printf "${COLOR_DEFAULT} - \"${containerName}\"\n" 465 | idx=$((idx+1)) 466 | done 467 | 468 | # 8. Check the "readonlyRootFilesystem" flag added in the task definition (red) 469 | readonlyRootFsList=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[].readonlyRootFilesystem") 470 | idx=0 471 | printf "${COLOR_DEFAULT} ----------\n" 472 | printf "${COLOR_DEFAULT} Read-Only Root Filesystem (${taskDefFamily}:${taskDefRevision})\n" 473 | printf "${COLOR_DEFAULT} ----------\n" 474 | for enabled in $readonlyRootFsList; do 475 | containerName=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[${idx}].name") 476 | printf " $((idx+1)). " 477 | case "${enabled}" in 478 | *false* ) printf "${COLOR_GREEN}Disabled";; 479 | *true* ) printf "${COLOR_RED}ReadOnly";; 480 | * ) printf "${COLOR_GREEN}Disabled";; 481 | esac 482 | printf "${COLOR_DEFAULT} - \"${containerName}\"\n" 483 | idx=$((idx+1)) 484 | done 485 | 486 | # 9. Check the task role permissions 487 | overriddenTaskRole=true 488 | taskRoleArn=$(echo "${describedTaskJson}" | jq -r ".tasks[0].overrides.taskRoleArn") 489 | if [[ "${taskRoleArn}" = "null" ]]; then 490 | overriddenTaskRole=false 491 | taskRoleArn=$(echo "${taskDefJson}" | jq -r ".taskDefinition.taskRoleArn") 492 | fi 493 | 494 | hasRole=true 495 | isEC2Role=false 496 | if [[ "${taskRoleArn}" = "null" ]]; then 497 | ## When the task runs on EC2 without a task role then we should check the instance profile 498 | if [[ "${launchType}" = "EC2" ]]; then 499 | ec2InstanceId=$(echo "${describedContainerInstanceJson}" | jq -r ".containerInstances[0].ec2InstanceId") 500 | instanceProfileArn=$(${AWS_CLI_BIN} ec2 describe-instances --instance-ids "${ec2InstanceId}" | jq -r ".Reservations[0].Instances[0].IamInstanceProfile.Arn") 501 | if [[ "${instanceProfileArn}" = "null" ]]; then 502 | hasRole=false 503 | else 504 | instanceProfileName=$(echo "${instanceProfileArn}" | sed 's/arn:aws:iam::.*:instance-profile\///g') 505 | taskRoleArn=$(${AWS_CLI_BIN} iam get-instance-profile \ 506 | --instance-profile-name "${instanceProfileName}" \ 507 | | jq -r ".InstanceProfile.Roles[0].Arn") 508 | if [[ "${taskRoleArn}" = "null" ]]; then 509 | hasRole=false 510 | else 511 | isEC2Role=true 512 | fi 513 | fi 514 | else 515 | ## Fargate launch type doesn't support to use EC2 instance roles 516 | hasRole=false 517 | fi 518 | fi 519 | 520 | if [[ ! "${hasRole}" = "true" ]]; then 521 | printf "${COLOR_DEFAULT} EC2 or Task Role | ${COLOR_RED}Not Configured\n" 522 | else 523 | if [[ "${isEC2Role}" = "true" ]]; then 524 | printf "${COLOR_DEFAULT} EC2 Role Permissions | " 525 | else 526 | printf "${COLOR_DEFAULT} Task Role Permissions | " 527 | fi 528 | printf "${taskRoleArn}" 529 | if [[ "${overriddenTaskRole}" = "true" ]]; then 530 | printf " (Overridden)" 531 | fi 532 | printf "\n" 533 | ## Required Permissions 534 | ### SSM 535 | ssm="ssmmessages:" 536 | ssmCreateControlChannel="${ssm}CreateControlChannel" 537 | ssmCreateDataChannel="${ssm}CreateDataChannel" 538 | ssmOpenControlChannel="${ssm}OpenControlChannel" 539 | ssmOpenDataChannel="${ssm}OpenDataChannel" 540 | 541 | ssmEvalResultsJson=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 542 | --policy-source-arn "${taskRoleArn}" \ 543 | --action-names "${ssmCreateControlChannel}" "${ssmCreateDataChannel}" "${ssmOpenControlChannel}" "${ssmOpenDataChannel}" \ 544 | --output json) 545 | ssmCreateControlChannelResult=$(readEvalDecision "${ssmEvalResultsJson}" "${ssmCreateControlChannel}") 546 | showEvalResult "${ssmCreateControlChannelResult}" "${ssmCreateControlChannel}" 547 | ssmCreateDataChannelResult=$(readEvalDecision "${ssmEvalResultsJson}" "${ssmCreateDataChannel}") 548 | showEvalResult "${ssmCreateDataChannelResult}" "${ssmCreateDataChannel}" 549 | ssmOpenControlChannelResult=$(readEvalDecision "${ssmEvalResultsJson}" "${ssmOpenControlChannel}") 550 | showEvalResult "${ssmOpenControlChannelResult}" "${ssmOpenControlChannel}" 551 | ssmOpenDataChannelResult=$(readEvalDecision "${ssmEvalResultsJson}" "${ssmOpenDataChannel}") 552 | showEvalResult "${ssmOpenDataChannelResult}" "${ssmOpenDataChannel}" 553 | 554 | ## Optional Permissions (Might be required if audit-logging is enabled) 555 | ### KMS 556 | if [[ ! "${kmsKeyId}" = "null" ]]; then 557 | printf "${COLOR_DEFAULT} -----\n" 558 | kmsDecrypt="kms:Decrypt" 559 | kmsEvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 560 | --policy-source-arn "${taskRoleArn}" \ 561 | --action-names "${kmsDecrypt}" \ 562 | --resource-arns "${kmsKeyArn}" \ 563 | --output json \ 564 | | jq -r ".EvaluationResults[0].EvalDecision") 565 | showEvalResult "${kmsEvalResult}" "${kmsDecrypt}" 566 | fi 567 | ### S3 Bucket 568 | if [[ ! "${s3BucketName}" = "null" ]]; then 569 | printf "${COLOR_DEFAULT} -----\n" 570 | s3PutObject="s3:PutObject" 571 | bucketArn="arn:aws:s3:::${s3BucketName}" 572 | resourceArn="" 573 | if [[ ! "${s3KeyPrefix}" = "null" ]]; then 574 | resourceArn="${bucketArn}/${s3KeyPrefix}*" 575 | else 576 | resourceArn="${bucketArn}/*" 577 | fi 578 | s3EvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 579 | --policy-source-arn "${taskRoleArn}" \ 580 | --action-names "${s3PutObject}" \ 581 | --resource-arns "${resourceArn}" \ 582 | --output json \ 583 | | jq -r ".EvaluationResults[0].EvalDecision") 584 | showEvalResult "${s3EvalResult}" "${s3PutObject}" 585 | if [[ "${s3Encryption}" = "true" ]]; then 586 | s3GetEncryptionConfiguration="s3:GetEncryptionConfiguration" 587 | s3EvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 588 | --policy-source-arn "${taskRoleArn}" \ 589 | --action-names "${s3GetEncryptionConfiguration}" \ 590 | --resource-arns "${bucketArn}" \ 591 | --output json \ 592 | | jq -r ".EvaluationResults[0].EvalDecision") 593 | showEvalResult "${s3EvalResult}" "${s3GetEncryptionConfiguration}" 594 | fi 595 | fi 596 | ### CloudWatch Logs 597 | if [[ ! "${cloudWatchLogGroupName}" = "null" ]]; then 598 | printf "${COLOR_DEFAULT} -----\n" 599 | # For Resource "*" 600 | logsDescribeLogGroup="logs:DescribeLogGroups" 601 | logsDescribeLogGroupEvalResult=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 602 | --policy-source-arn "${taskRoleArn}" \ 603 | --action-names "${logsDescribeLogGroup}" \ 604 | --output json \ 605 | | jq -r ".EvaluationResults[0].EvalDecision") 606 | showEvalResult "${logsDescribeLogGroupEvalResult}" "${logsDescribeLogGroup}" 607 | # For Resource "${cloudWatchLogGroupName}" 608 | cwlogGroupArn="arn:aws:logs:${AWS_REGION}:${ACCOUNT_ID}:log-group:${cloudWatchLogGroupName}:*" 609 | logsCreateLogStream="logs:CreateLogStream" 610 | logsDescribeLogStreams="logs:DescribeLogStreams" 611 | logsPutLogEvents="logs:PutLogEvents" 612 | logsEvalResultsJson=$(${AWS_CLI_BIN} iam simulate-principal-policy \ 613 | --policy-source-arn "${taskRoleArn}" \ 614 | --action-names "${logsCreateLogStream}" "${logsDescribeLogStreams}" "${logsPutLogEvents}" \ 615 | --resource-arns "${cwlogGroupArn}" \ 616 | --output json) 617 | logsCreateLogStreamResult=$(readEvalDecision "${logsEvalResultsJson}" "${logsCreateLogStream}") 618 | showEvalResult "${logsCreateLogStreamResult}" "${logsCreateLogStream}" 619 | logsDescribeLogStreamsResult=$(readEvalDecision "${logsEvalResultsJson}" "${logsDescribeLogStreams}") 620 | showEvalResult "${logsDescribeLogStreamsResult}" "${logsDescribeLogStreams}" 621 | logsPutLogEventsResult=$(readEvalDecision "${logsEvalResultsJson}" "${logsPutLogEvents}") 622 | showEvalResult "${logsPutLogEventsResult}" "${logsPutLogEvents}" 623 | fi 624 | fi 625 | 626 | # 10. Check existing VPC Endpoints (PrivateLinks) in the task VPC. 627 | # If there is any VPC Endpoints configured for the task VPC, we assume you would need an additional SSM PrivateLink to be configured. (yellow) 628 | # TODO: In the ideal world, the script should simply check if the task can reach to the internet or not :) 629 | requiredEndpoint="com.amazonaws.${AWS_REGION}.ssmmessages" 630 | taskNetworkingAttachment=$(echo "${describedTaskJson}" | jq -r ".tasks[0].attachments[0]") 631 | if [[ "${taskNetworkingAttachment}" = "null" ]]; then 632 | ## bridge/host networking (only for EC2) 633 | taskVpcId=$(echo "${describedContainerInstanceJson}" | jq -r ".containerInstances[0].attributes[] | select(.name==\"ecs.vpc-id\") | .value") 634 | taskSubnetId=$(echo "${describedContainerInstanceJson}" | jq -r ".containerInstances[0].attributes[] | select(.name==\"ecs.subnet-id\") | .value") 635 | subnetJson=$(${AWS_CLI_BIN} ec2 describe-subnets --subnet-ids "${taskSubnetId}") 636 | else 637 | ## awsvpc networking (for both EC2 and Fargate) 638 | taskSubnetId=$(echo "${describedTaskJson}" | jq -r ".tasks[0].attachments[0].details[] | select(.name==\"subnetId\") | .value") 639 | subnetJson=$(${AWS_CLI_BIN} ec2 describe-subnets --subnet-ids "${taskSubnetId}") 640 | taskVpcId=$(echo "${subnetJson}" | jq -r ".Subnets[0].VpcId") 641 | fi 642 | ## Obtain the ownerID of subnet's owner to check if the subnet is shared via AWS RAM (which check-ecs-exec.sh doesn't support today) 643 | subnetOwnerId=$(echo "${subnetJson}" | jq -r ".Subnets[0].OwnerId") 644 | printf "${COLOR_DEFAULT} VPC Endpoints | " 645 | if [[ ! "${ACCOUNT_ID}" = "${subnetOwnerId}" ]]; then 646 | ## Shared Subnets (VPC) are not supported in Amazon ECS Exec Checker 647 | printf "${COLOR_RED}CHECK FAILED${COLOR_YELLOW}\n" 648 | printf " Amazon ECS Exec Checker doesn't support VPC endpoint validation for AWS RAM shared VPC/subnets.\n" 649 | printf " Check or contact your administrator to find if additional VPC endpoints are required by the following resources.\n" 650 | printf " - Resources: ${taskVpcId} and ${taskSubnetId}\n" 651 | printf " - VPC Endpoint: ${requiredEndpoint}${COLOR_DEFAULT}\n" 652 | else 653 | ## List Vpc Endpoints 654 | vpcEndpointsJson=$(${AWS_CLI_BIN} ec2 describe-vpc-endpoints \ 655 | --filters Name=vpc-id,Values="${taskVpcId}") 656 | vpcEndpoints=$(echo "${vpcEndpointsJson}" | tr -d '\n' | jq -r ".VpcEndpoints[]") 657 | if [[ "${vpcEndpoints}" = "" ]]; then 658 | printf "${COLOR_GREEN}SKIPPED ${COLOR_DEFAULT}(${taskVpcId} - No additional VPC endpoints required)\n" 659 | else 660 | # Check whether an ssmmessages VPC endpoint exists 661 | vpcEndpoints=$(echo "${vpcEndpointsJson}" | tr -d '\n' | jq -r ".VpcEndpoints[].ServiceName") 662 | printf "\n" 663 | ssmsessionVpcEndpointExists=false 664 | for vpe in $vpcEndpoints; do 665 | if [[ "${vpe}" = "${requiredEndpoint}" ]]; then 666 | ssmsessionVpcEndpointExists=true 667 | break 668 | fi 669 | done 670 | 671 | printf " Found existing endpoints for ${taskVpcId}:\n" 672 | for vpe in $vpcEndpoints; do 673 | if [[ "${vpe}" = "${requiredEndpoint}" ]]; then 674 | printf " - ${COLOR_GREEN}${vpe}${COLOR_DEFAULT}\n" 675 | else 676 | printf " - ${COLOR_DEFAULT}${vpe}\n" 677 | fi 678 | done 679 | if [[ "${ssmsessionVpcEndpointExists}" = "false" ]]; then 680 | printf " SSM PrivateLink \"${COLOR_YELLOW}${requiredEndpoint}${COLOR_DEFAULT}\" not found. You must ensure your task has proper outbound internet connectivity." 681 | fi 682 | fi 683 | fi 684 | 685 | # 11. Check task definition containers for environment variables AWS_ACCESS_KEY, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY 686 | # if AWS_ACCESS_KEY, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY are defined in a container, they will be used by the SSM service 687 | # if the key defined does not have requirement permissions, the execute-command will not work. 688 | containerNameList=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[].name") 689 | idx=0 690 | printf "${COLOR_DEFAULT} Environment Variables | (${taskDefFamily}:${taskDefRevision})\n" 691 | for containerName in $containerNameList; do 692 | printf " ${COLOR_DEFAULT}$((idx+1)). container \"${containerName}\"\n" 693 | # find AWS_ACCESS_KEY 694 | printf " ${COLOR_DEFAULT}- AWS_ACCESS_KEY" 695 | AWS_ACCESS_KEY_FOUND=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[${idx}].environment[] | select(.name==\"AWS_ACCESS_KEY\") | .name") 696 | case "${AWS_ACCESS_KEY_FOUND}" in 697 | *AWS_ACCESS_KEY* ) printf ": ${COLOR_YELLOW}defined${COLOR_DEFAULT}\n";; 698 | * ) printf ": ${COLOR_GREEN}not defined${COLOR_DEFAULT}\n";; 699 | esac 700 | # find AWS_ACCESS_KEY_ID 701 | printf " ${COLOR_DEFAULT}- AWS_ACCESS_KEY_ID" 702 | AWS_ACCESS_KEY_ID_FOUND=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[${idx}].environment[] | select(.name==\"AWS_ACCESS_KEY_ID\") | .name") 703 | case "${AWS_ACCESS_KEY_ID_FOUND}" in 704 | *AWS_ACCESS_KEY_ID* ) printf ": ${COLOR_YELLOW}defined${COLOR_DEFAULT}\n";; 705 | * ) printf ": ${COLOR_GREEN}not defined${COLOR_DEFAULT}\n";; 706 | esac 707 | # find AWS_SECRET_ACCESS_KEY 708 | printf " ${COLOR_DEFAULT}- AWS_SECRET_ACCESS_KEY" 709 | AWS_SECRET_ACCESS_KEY_FOUND=$(echo "${taskDefJson}" | jq -r ".taskDefinition.containerDefinitions[${idx}].environment[] | select(.name==\"AWS_SECRET_ACCESS_KEY\") | .name") 710 | case "${AWS_SECRET_ACCESS_KEY_FOUND}" in 711 | *AWS_SECRET_ACCESS_KEY* ) printf ": ${COLOR_YELLOW}defined${COLOR_DEFAULT}\n";; 712 | * ) printf ": ${COLOR_GREEN}not defined${COLOR_DEFAULT}\n";; 713 | esac 714 | idx=$((idx+1)) 715 | done 716 | 717 | # 12. Check PID mode 718 | pidMode=$(echo "${taskDefJson}" | jq -r ".taskDefinition.pidMode") 719 | printf "${COLOR_DEFAULT} PidMode | " 720 | if [[ ${pidMode} = "task" ]]; then 721 | printf "${COLOR_YELLOW}${pidMode} \n" 722 | elif [[ ${pidMode} = "host" ]]; then 723 | printf "${COLOR_GREEN}${pidMode} \n" 724 | else 725 | printf "${COLOR_GREEN}Not Configured \n" 726 | fi 727 | 728 | printf "\n" 729 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/amazon-ecs-exec-checker/b31bb8c65f76d580f94d314ac202ae92052e57ee/demo.gif -------------------------------------------------------------------------------- /example-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/amazon-ecs-exec-checker/b31bb8c65f76d580f94d314ac202ae92052e57ee/example-result.png --------------------------------------------------------------------------------