How To SSH Into An ECS Fargate Container
Tags: aws, docker, cloud, serverless
Published: 2022-07-07 08:03:21

## The Problem
Best practices dictate that you should not SSH into containers; instead, you should implement proper observability mechanisms for monitoring, debugging, and log analysis. So it was impossible with ECS, at least not until the 16th of March 2021. After that, it was a highly requested feature on the AWS Container roadmap. AWS eventually gave in because they understood that engineers must be able to debug and test quickly in the early stages of development. It can also be helpful to debug high severity issues in production.

## The Solution
Before getting into your ECS container, you would have to open the SSH ports, get keys/passwords to the host instance, SSH into that instance, and then `docker exec` into the container. If you were running the serverless Fargate model, you couldn't even do this because you do not have access to the host machine, so you'd have to redeploy it to an EC2 instance.
![Superman on the floor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz3v4x1lm2rmjyjplv3s.png)
 
Then ECS exec came to the rescue, and it allows you to `docker exec` right into the container you want without the overhead of worrying about security issues with distributing SSH keys or passwords
![Superman being a hero](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0i4keb2f9xyy5ygtl5pm.png)

## Implementation
I will use the Fargate container from [this post](https://dev.to/femilawal/creating-an-ecs-cluster-with-cloudformation-54cg) and make modifications to specific parts of the CloudFormation template.
- IAM Policy for the Task Role
- KMS key to encrypt the ECS Exec data channel
- Enable Execute command
- Place cluster in public subnets
The template already has the cluster in public subnets, so first, we'll add the IAM Policy.
``` yaml
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ["-", [!Ref ServiceName, TaskRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "systems-manager"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "ssmmessages:CreateControlChannel"
                  - "ssmmessages:CreateDataChannel"
                  - "ssmmessages:OpenControlChannel"
                  - "ssmmessages:OpenDataChannel"
                Resource: "*"
              - Effect: "Allow"
                Action:
                  - "kms:Decrypt"
                Resource: !Ref KMSKey
```
Then the KMS key:
```yaml
  ############################################################
  #              KMS KEY
  ############################################################
  KMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: Key needed to encrypt docker exec data channel
      KeyPolicy:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action:
              - "kms:*"
            Resource: "*"
```
Now we enable the exec command on the service, that can be done by adding `EnableExecuteCommand: true`
```yaml
  ############################################################
  #              ECS SERVICE
  ############################################################
  Service:
    Type: AWS::ECS::Service
    # This dependency is needed so that the load balancer is setup correctly in time
    DependsOn:
      - ApplicationLoadBalancer
      - ALBHTTPListener
      - ListenerRule
      - TargetGroup
    Properties:
      ServiceName: !Ref ServiceName
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      EnableExecuteCommand: true        
      DeploymentConfiguration:
        MinimumHealthyPercent: 100
        MaximumPercent: 200
      DesiredCount: !Ref MinContainers
      HealthCheckGracePeriodSeconds: 300
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref PublicSubnetOne
            - !Ref PublicSubnetTwo
          SecurityGroups:
            - !Ref ContainerSecurityGroup
      LoadBalancers:
        - ContainerName: !Ref ServiceName
          ContainerPort: !Ref ContainerPort
          TargetGroupArn: !Ref TargetGroup
```
You can deploy the [template](https://gist.github.com/Femi-lawal/1a542fc3f3f782db615d2bb028e49cc6):
```bash
aws cloudformation deploy --template-file ecs.yml --stack-name ecs-infrastructure --capabilities CAPABILITY_NAMED_IAM --profile default
```
![Deploy output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3wume9ww9yonnh6ciu6.png)
Then you can get the Task Id by running:
```bash
aws ecs list-tasks --cluster rest-api-cluster
```
The task id is the part highlighted in red below:
![Task Id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ib00er1t3fodf7ia1gh7.png)
 
This is an alpine linux docker image, so instead of `"/bin/bash"`, to docker exec into it, `"/bin/sh"` will have to be used instead.
```bash
aws ecs execute-command     --region us-east-1    --cluster rest-api-cluster     --task INSERT_YOUR_TASK_ID_HERE     --container rest-api     --command "/bin/sh"     --interactive
```

Sample output: 
![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtt6p9rgnflg81urgadr.png)
You can use [this tool](https://github.com/aws-containers/amazon-ecs-exec-checker) to troubleshoot issues.


## Cleanup
Always remember to delete resources you do not need, run:
```bash
aws cloudformation delete-stack --stack-name ecs-infrastructure
```
You can check the status of the stack to confirm if it was deleted successfully:
```bash 
aws cloudformation describe-stacks --stack-name ecs-infrastructure
```
![Sample output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv6ppmns2r6jvbf33wiw.png)
Confirm the stack was deleted successfully; remember to clean up the ECR repository if you implemented that.