add private images support

Signed-off-by: aiordache <anca.iordache@docker.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
aiordache 2020-05-05 18:55:03 +02:00 committed by Nicolas De Loof
parent 57d7474f7d
commit d09c8c7236
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
8 changed files with 119 additions and 36 deletions

View File

@ -16,7 +16,10 @@ import (
) )
type createSecretOptions struct { type createSecretOptions struct {
Label string Label string
Username string
Password string
Description string
} }
type deleteSecretOptions struct { type deleteSecretOptions struct {
@ -39,9 +42,9 @@ func SecretCommand(dockerCli command.Cli) *cobra.Command {
} }
func CreateSecret(dockerCli command.Cli) *cobra.Command { func CreateSecret(dockerCli command.Cli) *cobra.Command {
//opts := createSecretOptions{} opts := createSecretOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create NAME SECRET", Use: "create NAME",
Short: "Creates a secret.", Short: "Creates a secret.",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error { RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region) client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
@ -52,12 +55,16 @@ func CreateSecret(dockerCli command.Cli) *cobra.Command {
return errors.New("Missing mandatory parameter: NAME") return errors.New("Missing mandatory parameter: NAME")
} }
name := args[0] name := args[0]
secret := args[1]
id, err := client.CreateSecret(context.Background(), name, secret) secret := docker.NewSecret(name, opts.Username, opts.Password, opts.Description)
id, err := client.CreateSecret(context.Background(), secret)
fmt.Println(id) fmt.Println(id)
return err return err
}), }),
} }
cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "username")
cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "password")
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Secret description")
return cmd return cmd
} }

View File

@ -55,17 +55,28 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
} }
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name) taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name)
policy, err := c.getPolicy(ctx, definition)
if err != nil {
return nil, err
}
rolePolicies := []iam.Role_Policy{}
if policy != nil {
rolePolicies = append(rolePolicies, iam.Role_Policy{
PolicyDocument: policy,
PolicyName: taskExecutionRole,
})
}
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
template.Resources[taskExecutionRole] = &iam.Role{ template.Resources[taskExecutionRole] = &iam.Role{
AssumeRolePolicyDocument: assumeRolePolicyDocument, AssumeRolePolicyDocument: assumeRolePolicyDocument,
// Here we can grant access to secrets/configs using a Policy { Allow,ssm:GetParameters,secret|config ARN} Policies: rolePolicies,
ManagedPolicyArns: []string{ ManagedPolicyArns: []string{
ECSTaskExecutionPolicy, ECSTaskExecutionPolicy,
}, },
} }
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
// FIXME definition.TaskRoleArn = ?
taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
template.Resources[taskDefinition] = definition template.Resources[taskDefinition] = definition
var healthCheck *cloudmap.Service_HealthCheckConfig var healthCheck *cloudmap.Service_HealthCheckConfig
@ -182,6 +193,33 @@ func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, e
return defaultVPC, nil return defaultVPC, nil
} }
func (c client) getPolicy(ctx context.Context, taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
arns := []string{}
for _, container := range taskDef.ContainerDefinitions {
if container.RepositoryCredentials != nil {
arns = append(arns, container.RepositoryCredentials.CredentialsParameter)
}
if len(container.Secrets) > 0 {
for _, s := range container.Secrets {
arns = append(arns, s.ValueFrom)
}
}
}
if len(arns) > 0 {
return &PolicyDocument{
Statement: []PolicyStatement{
{
Effect: "Allow",
Action: []string{"secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt"},
Resource: arns,
}},
}, nil
}
return nil, nil
}
type convertAPI interface { type convertAPI interface {
GetDefaultVPC(ctx context.Context) (string, error) GetDefaultVPC(ctx context.Context) (string, error)
VpcExists(ctx context.Context, vpcID string) (bool, error) VpcExists(ctx context.Context, vpcID string) (bool, error)

View File

@ -44,7 +44,7 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe
FirelensConfiguration: nil, FirelensConfiguration: nil,
HealthCheck: toHealthCheck(service.HealthCheck), HealthCheck: toHealthCheck(service.HealthCheck),
Hostname: service.Hostname, Hostname: service.Hostname,
Image: service.Image, Image: getImage(service.Image),
Interactive: false, Interactive: false,
Links: nil, Links: nil,
LinuxParameters: toLinuxParameters(service), LinuxParameters: toLinuxParameters(service),
@ -282,22 +282,27 @@ func toKeyValuePair(environment types.MappingWithEquals) []ecs.TaskDefinition_Ke
return pairs return pairs
} }
func getImage(image string) string {
switch f := strings.Split(image, "/"); len(f) {
case 1:
return "docker.io/library/" + image
case 2:
return "docker.io/" + image
default:
return image
}
}
func getRepoCredentials(service types.ServiceConfig) (*ecs.TaskDefinition_RepositoryCredentials, error) { func getRepoCredentials(service types.ServiceConfig) (*ecs.TaskDefinition_RepositoryCredentials, error) {
// extract registry and namespace string from image name // extract registry and namespace string from image name
fields := strings.Split(service.Image, "/") credential := ""
regPath := "" for key, value := range service.Extras {
for i, field := range fields { if strings.HasPrefix(key, "x-aws-pull_credentials") {
if i < len(fields)-1 { credential = value.(string)
regPath = regPath + field
} }
} }
if regPath == "" || len(service.Secrets) == 0 { if credential != "" {
return nil, nil return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: credential}, nil
}
for _, secret := range service.Secrets {
if secret.Source == regPath {
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: secret.Target}, nil
}
} }
return nil, nil return nil, nil
} }
@ -306,7 +311,7 @@ func getSecrets(service types.ServiceConfig) ([]ecs.TaskDefinition_Secret, error
secrets := []ecs.TaskDefinition_Secret{} secrets := []ecs.TaskDefinition_Secret{}
for _, secret := range service.Secrets { for _, secret := range service.Secrets {
secrets = append(secrets, ecs.TaskDefinition_Secret{Name: secret.Target}) secrets = append(secrets, ecs.TaskDefinition_Secret{Name: secret.Target, ValueFrom: secret.Source})
} }
return secrets, nil return secrets, nil
} }

View File

@ -6,11 +6,12 @@ package mock
import ( import (
context "context" context "context"
reflect "reflect"
cloudformation "github.com/aws/aws-sdk-go/service/cloudformation" cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
cloudformation0 "github.com/awslabs/goformation/v4/cloudformation" cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
docker "github.com/docker/ecs-plugin/pkg/docker" docker "github.com/docker/ecs-plugin/pkg/docker"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
reflect "reflect"
) )
// MockAPI is a mock of API interface // MockAPI is a mock of API interface
@ -67,18 +68,18 @@ func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Cal
} }
// CreateSecret mocks base method // CreateSecret mocks base method
func (m *MockAPI) CreateSecret(arg0 context.Context, arg1, arg2 string) (string, error) { func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 docker.Secret) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1, arg2) ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// CreateSecret indicates an expected call of CreateSecret // CreateSecret indicates an expected call of CreateSecret
func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1, arg2 interface{}) *gomock.Call { func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 docker.Secret) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1)
} }
// CreateStack mocks base method // CreateStack mocks base method

View File

@ -223,9 +223,18 @@ func (s sdk) DeleteStack(ctx context.Context, name string) error {
return err return err
} }
func (s sdk) CreateSecret(ctx context.Context, name string, secret string) (string, error) { func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
logrus.Debug("Create secret " + name) logrus.Debug("Create secret " + secret.Name)
response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{Name: &name, SecretString: &secret}) secretStr, err := secret.GetCredString()
if err != nil {
return "", err
}
response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{
Name: &secret.Name,
SecretString: &secretStr,
Description: &secret.Description,
})
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -7,14 +7,14 @@ import (
) )
type secretsAPI interface { type secretsAPI interface {
CreateSecret(ctx context.Context, name string, content string) (string, error) CreateSecret(ctx context.Context, secret docker.Secret) (string, error)
InspectSecret(ctx context.Context, id string) (docker.Secret, error) InspectSecret(ctx context.Context, id string) (docker.Secret, error)
ListSecrets(ctx context.Context) ([]docker.Secret, error) ListSecrets(ctx context.Context) ([]docker.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error DeleteSecret(ctx context.Context, id string, recover bool) error
} }
func (c client) CreateSecret(ctx context.Context, name string, content string) (string, error) { func (c client) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
return c.api.CreateSecret(ctx, name, content) return c.api.CreateSecret(ctx, secret)
} }
func (c client) InspectSecret(ctx context.Context, id string) (docker.Secret, error) { func (c client) InspectSecret(ctx context.Context, id string) (docker.Secret, error) {

View File

@ -12,7 +12,7 @@ type API interface {
ComposeUp(ctx context.Context, project *Project) error ComposeUp(ctx context.Context, project *Project) error
ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error
CreateSecret(ctx context.Context, name string, secret string) (string, error) CreateSecret(ctx context.Context, secret docker.Secret) (string, error)
InspectSecret(ctx context.Context, id string) (docker.Secret, error) InspectSecret(ctx context.Context, id string) (docker.Secret, error)
ListSecrets(ctx context.Context) ([]docker.Secret, error) ListSecrets(ctx context.Context) ([]docker.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error DeleteSecret(ctx context.Context, id string, recover bool) error

View File

@ -9,6 +9,17 @@ type Secret struct {
Name string `json:"Name"` Name string `json:"Name"`
Labels map[string]string `json:"Labels"` Labels map[string]string `json:"Labels"`
Description string `json:"Description"` Description string `json:"Description"`
username string
password string
}
func NewSecret(name, username, password, description string) Secret {
return Secret{
Name: name,
username: username,
password: password,
Description: description,
}
} }
func (s Secret) ToJSON() (string, error) { func (s Secret) ToJSON() (string, error) {
@ -18,3 +29,15 @@ func (s Secret) ToJSON() (string, error) {
} }
return string(b), nil return string(b), nil
} }
func (s Secret) GetCredString() (string, error) {
creds := map[string]string{
"username": s.username,
"password": s.password,
}
b, err := json.Marshal(&creds)
if err != nil {
return "", err
}
return string(b), nil
}