Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local create, Part the First #785

Merged
merged 33 commits into from
Jun 18, 2019
Merged

Local create, Part the First #785

merged 33 commits into from
Jun 18, 2019

Conversation

SoManyHs
Copy link
Contributor

@SoManyHs SoManyHs commented Jun 10, 2019

Issue #, if available:

Description of changes:


Enter [N/A] in the box, if an item is not applicable to your change.

Testing

  • Unit tests passed
  • Integration tests passed
  • Unit tests added for new functionality
  • Listed manual checks and their outputs in the comments (example)
  • Link to issue or PR for the integration tests:

Documentation

  • [N/A ] Contacted our doc writer
  • [N/A] Updated our README

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

SoManyHs added 25 commits May 29, 2019 15:41
$ dep ensure -update github.com/docker/go-units
TODO: fix terrible hack on convertLinuxParameters
These fields have the `yaml:",omitempty"` specification, so they will not be written
into the docker compose yaml as long as the values are empty.
For many of the ServiceConfig fields, an empty data structure is the
same as nil as far as the Marshaller is concerned, so explicitly
returning nil on an empty input is unnecessary.
TODO access context to create SSM/SM clients
- Create LocalProject interface, which holds the cli.Context
- Acounts for -o flag for specifying custom docker compose file to
write to
@SoManyHs SoManyHs requested a review from efekarakus June 10, 2019 04:02
@SoManyHs
Copy link
Contributor Author

For the following task-definition.json:

{
   "containerDefinitions": [
      {
         "image": "nginx",
         "name": "web"
      },
      {
         "command": [
            "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' >  /usr/local/apache2/htdocs/index.html && httpd-foreground\""
         ],
         "entryPoint": [
            "sh",
            "-c"
         ],
         "essential": true,
         "image": "httpd:2.4",
         "logConfiguration": {
            "logDriver": "awslogs",
            "options": {
               "awslogs-group" : "/ecs/fargate-task-definition",
               "awslogs-region": "us-east-1",
               "awslogs-stream-prefix": "ecs"
            }
         },
	 "secrets": [{
		 "name": "MY_SECRET",
		 "valueFrom": "arn:aws:secretsmanager:us-west-2:xxxxxxxxxxx51:secret:MY_SECRET"
	 }],
         "name": "sample-fargate-app",
         "portMappings": [
            {
               "containerPort": 80,
               "hostPort": 80,
               "protocol": "tcp"
            }
         ]
      }
   ],
   "cpu": "256",
   "executionRoleArn": "arn:aws:iam::012345678910:role/ecsTaskExecutionRole",
   "family": "fargate-task-definition",
   "memory": "512",
   "networkMode": "awsvpc",
   "requiresCompatibilities": [
       "FARGATE"
    ]
}

Running ecs-cli local create (looks for task-definition.json in current dir, writes to docker-compose.local.yml)

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create
Successfully wrote ./docker-compose.local.yml

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ cat docker-compose.local.yml 
version: "3.0"
services:
  sample-fargate-app:
    command:
    - '/bin/sh -c "echo ''<html> <head> <title>Amazon ECS Sample App</title> <style>body
      {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center>
      <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application
      is now running on a container in Amazon ECS.</p> </div></body></html>'' >  /usr/local/apache2/htdocs/index.html
      && httpd-foreground"'
    entrypoint:
    - sh
    - -c
    environment:
      MY_SECRET: secretsmanager
    image: httpd:2.4
    logging:
      driver: awslogs
      options:
        awslogs-group: /ecs/fargate-task-definition
        awslogs-region: us-east-1
        awslogs-stream-prefix: ecs
    ports:
    - target: 80
      published: 80
      protocol: tcp
  web:
    image: nginx

If local create is re-run:

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create
./docker-compose.local.yml file already exists. Do you want to write over this file? [y/N]
n
FATA[0002] Error with local create: Aborted writing compose file. To retry, rename or move ./docker-compose.local.yml 

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create
./docker-compose.local.yml file already exists. Do you want to write over this file? [y/N]
y
Successfully wrote ./docker-compose.local.yml

With output flag:

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create -o foo.yml
Successfully wrote foo.yml

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ cat foo.yml 
version: "3.0"
services:
  sample-fargate-app:
    command:
    - '/bin/sh -c "echo ''<html> <head> <title>Amazon ECS Sample App</title> <style>body
      {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center>
      <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application
      is now running on a container in Amazon ECS.</p> </div></body></html>'' >  /usr/local/apache2/htdocs/index.html
      && httpd-foreground"'
    entrypoint:
    - sh
    - -c
    environment:
      MY_SECRET: secretsmanager
    image: httpd:2.4
    logging:
      driver: awslogs
      options:
        awslogs-group: /ecs/fargate-task-definition
        awslogs-region: us-east-1
        awslogs-stream-prefix: ecs
    ports:
    - target: 80
      published: 80
      protocol: tcp
  web:
    image: nginx

@SoManyHs
Copy link
Contributor Author

SoManyHs commented Jun 10, 2019

Using --arn flag (to pull from registered task definition):

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ aws ecs describe-task-definition --task-definition test_fields
{
    "taskDefinition": {
        "status": "ACTIVE",
        "family": "test_fields",
        "placementConstraints": [],
        "requiresAttributes": [
            {
                "name": "com.amazonaws.ecs.capability.docker-remote-api.1.17"
            }
        ],
        "compatibilities": [
            "EC2"
        ],
        "volumes": [],
        "requiresCompatibilities": [
            "EC2"
        ],
        "taskDefinitionArn": "arn:aws:ecs:us-west-2:xxxxxxxxxxx51:task-definition/test_fields:71",
        "containerDefinitions": [
            {
                "dnsSearchDomains": [],
                "environment": [],
                "readonlyRootFilesystem": false,
                "pseudoTerminal": true,
                "name": "web",
                "links": [],
                "mountPoints": [],
                "image": "amazon/amazon-ecs-sample",
                "linuxParameters": {
                    "devices": [],
                    "capabilities": {}
                },
                "essential": true,
                "portMappings": [
                    {
                        "protocol": "tcp",
                        "containerPort": 80,
                        "hostPort": 80
                    }
                ],
                "dnsServers": [],
                "dockerSecurityOptions": [],
                "entryPoint": [],
                "privileged": false,
                "memory": 512,
                "command": [],
                "extraHosts": [],
                "cpu": 0,
                "volumesFrom": []
            }
        ],
        "revision": 71
    }
}

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create -a test_fields
./docker-compose.local.yml file already exists. Do you want to write over this file? [y/N]
y
Successfully wrote ./docker-compose.local.yml

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ cat docker-compose.local.yml
version: "3.0"
services:
  web:
    image: amazon/amazon-ecs-sample
    ports:
    - target: 80
      published: 80
      protocol: tcp
    tty: true

@SoManyHs
Copy link
Contributor Author

Using --file flag for task definition:

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ ecs-cli local create --file foo.json 
./docker-compose.local.yml file already exists. Do you want to write over this file? [y/N]
y
Successfully wrote ./docker-compose.local.yml

~/go/src/github.com/aws/amazon-ecs-cli(local-create)$ cat docker-compose.local.yml 
version: "3.0"
services:
  sample-fargate-app:
    command:
    - '/bin/sh -c "echo ''<html> <head> <title>Amazon ECS Sample App</title> <style>body
      {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center>
      <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application
      is now running on a container in Amazon ECS.</p> </div></body></html>'' >  /usr/local/apache2/htdocs/index.html
      && httpd-foreground"'
    entrypoint:
    - sh
    - -c
    environment:
      MY_SECRET: secretsmanager
    image: httpd:2.4
    logging:
      driver: awslogs
      options:
        awslogs-group: /ecs/fargate-task-definition
        awslogs-region: us-east-1
        awslogs-stream-prefix: ecs
    ports:
    - target: 80
      published: 80
      protocol: tcp
  web:
    image: nginx

@efekarakus efekarakus requested a review from a team June 10, 2019 19:49
ecs-cli/modules/cli/local/create_app.go Show resolved Hide resolved
ecs-cli/modules/cli/local/project/project.go Outdated Show resolved Hide resolved
ecs-cli/modules/cli/local/project/project.go Show resolved Hide resolved
ecs-cli/modules/cli/local/project/project.go Show resolved Hide resolved
ecs-cli/modules/cli/local/project/project.go Outdated Show resolved Hide resolved
ecs-cli/modules/cli/local/project/project.go Outdated Show resolved Hide resolved
func (c *secretsManagerClient) GetSecretValue(secretName string) (string, error) {
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(secretName),
VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything in particular that's confusing about this? We don't do this for other wrapped API methods on clients, but happy to make this clearer somehow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, nothing is confusing. I just had to look it up online to see if they had an explanation for "AWSCURRENT". I like providing a //See <link>, but it's a nitpick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that it's the default value, I'm guessing we can probably remove this entirely? I lifted a lot of this code from the Secrets Manager console but i'm wondering now if it's even necessary.

ecs-cli/modules/clients/aws/secretsmanager/client.go Outdated Show resolved Hide resolved

client := secretsmanager.NewSecretsManagerClient(commandConfig)

secret, err := client.GetSecretValue(secretName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dumb question: Is it okay for us to retrieve the value of their secret and output it in a file? Is there some sort of security concern here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call is tied to their config, so nobody would be able to do this unless they have the customer's creds. No different from how you can display a secret value in the Secrets Manager console. But maybe ping @allisaurus on this?

Copy link
Contributor

@allisaurus allisaurus Jun 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I share @efekarakus 's concerns re: outputting the secret contents to the file - I don't think the comparison to the console holds b/c now we are creating something that's persisting on the file system. Is there a way we can set the value as an env var and reference that var in the generated compose file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing to env vars and writing to a file aren't really that different from a security perspective -- you could argue writing to an environment variable where other processes might read from it might be worse. One thing that might mitigate this a bit is to change the file permissions to 400 or 600, so only the current user has read/write permissions and no one else does.

In any case, I don't believe there is significant risk, but we could add a log message that says that we'll be pulling and writing the decrypting a secret to the file just to make this more obvious.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense! We'll consult with others offline to discuss what would be the right approach to move forward.

One of the other recommendations suggested by @hencrice is to pass the secrets as environment variables to the docker-compose up command with local up. So something like NAME=secret docker-compose -f docker-compose.local.yml up -d. We'll check if this would be a good approach.

In the mean time to unblock you, what do you think about keeping the client.GetSecretValue as is because it's guaranteed to be used but deleting this function?

ecs-cli/modules/cli/local/project/project.go Show resolved Hide resolved
@SoManyHs SoManyHs requested a review from allisaurus June 10, 2019 21:17
SoManyHs added 3 commits June 10, 2019 18:10
Refactored Write to separate IO functionality from main caller, for
easier testing
Since this file could potentialy include decrypted secrets, we don't
want this to be readable by other users.

TODO: allow file permissions to be configurable?
Copy link
Contributor

@efekarakus efekarakus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good to me, just two questions and the rest are conflicts that can emerge with @iamhopaul123's changes.

arn := p.context.String(flags.TaskDefinitionArnFlag)
filename := p.context.String(flags.TaskDefinitionFileFlag)

if arn != "" && filename != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: @iamhopaul123 is also making modifications for ECS local :) he needed this same logic in local ps and local down so it was moved to a function here: https://github.com/aws/amazon-ecs-cli/pull/789/files#diff-d09e4b573b1ca106569f2a8dfcad6cd0R50

}

if taskDefinition == nil {
return fmt.Errorf("Could not detect valid Task Definition")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The recommendation for errors is to start with lowercase: https://github.com/golang/go/wiki/CodeReviewComments#error-strings

return config.NewCommandConfig(context, rdwr)
}

// FIXME: NOTE this will actually read from either ARN or Task Definition family name
Copy link
Contributor

@efekarakus efekarakus Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


client := secretsmanager.NewSecretsManagerClient(commandConfig)

secret, err := client.GetSecretValue(secretName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense! We'll consult with others offline to discuss what would be the right approach to move forward.

One of the other recommendations suggested by @hencrice is to pass the secrets as environment variables to the docker-compose up command with local up. So something like NAME=secret docker-compose -f docker-compose.local.yml up -d. We'll check if this would be a good approach.

In the mean time to unblock you, what do you think about keeping the client.GetSecretValue as is because it's guaranteed to be used but deleting this function?

ecs-cli/modules/clients/aws/secretsmanager/client.go Outdated Show resolved Hide resolved
const (
LocalOutDefaultFileName = "./docker-compose.local.yml"
LocalOutFileMode = os.FileMode(0644) // Owner=read/write, Other=readonly
LocalInFileName = "./task-definition.json"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason why we didn't address this comment? :p

@SoManyHs SoManyHs changed the title Local create Local create, Part the First Jun 18, 2019
Copy link
Contributor

@efekarakus efekarakus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked offline, we will address this comment as follow-up: #785 (comment)

@SoManyHs SoManyHs merged commit 86efe88 into aws:ecs-local Jun 18, 2019
@hencrice hencrice mentioned this pull request Jun 19, 2019
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants