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 {
Label string
Label string
Username string
Password string
Description string
}
type deleteSecretOptions struct {
@ -39,9 +42,9 @@ func SecretCommand(dockerCli command.Cli) *cobra.Command {
}
func CreateSecret(dockerCli command.Cli) *cobra.Command {
//opts := createSecretOptions{}
opts := createSecretOptions{}
cmd := &cobra.Command{
Use: "create NAME SECRET",
Use: "create NAME",
Short: "Creates a secret.",
RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
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")
}
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)
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
}

View File

@ -55,17 +55,28 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
}
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{
AssumeRolePolicyDocument: assumeRolePolicyDocument,
// Here we can grant access to secrets/configs using a Policy { Allow,ssm:GetParameters,secret|config ARN}
Policies: rolePolicies,
ManagedPolicyArns: []string{
ECSTaskExecutionPolicy,
},
}
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
// FIXME definition.TaskRoleArn = ?
taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
template.Resources[taskDefinition] = definition
var healthCheck *cloudmap.Service_HealthCheckConfig
@ -182,6 +193,33 @@ func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, e
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 {
GetDefaultVPC(ctx context.Context) (string, 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,
HealthCheck: toHealthCheck(service.HealthCheck),
Hostname: service.Hostname,
Image: service.Image,
Image: getImage(service.Image),
Interactive: false,
Links: nil,
LinuxParameters: toLinuxParameters(service),
@ -282,22 +282,27 @@ func toKeyValuePair(environment types.MappingWithEquals) []ecs.TaskDefinition_Ke
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) {
// extract registry and namespace string from image name
fields := strings.Split(service.Image, "/")
regPath := ""
for i, field := range fields {
if i < len(fields)-1 {
regPath = regPath + field
credential := ""
for key, value := range service.Extras {
if strings.HasPrefix(key, "x-aws-pull_credentials") {
credential = value.(string)
}
}
if regPath == "" || len(service.Secrets) == 0 {
return nil, nil
}
for _, secret := range service.Secrets {
if secret.Source == regPath {
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: secret.Target}, nil
}
if credential != "" {
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: credential}, nil
}
return nil, nil
}
@ -306,7 +311,7 @@ func getSecrets(service types.ServiceConfig) ([]ecs.TaskDefinition_Secret, error
secrets := []ecs.TaskDefinition_Secret{}
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
}

View File

@ -6,11 +6,12 @@ package mock
import (
context "context"
reflect "reflect"
cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
docker "github.com/docker/ecs-plugin/pkg/docker"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockAPI is a mock of API interface
@ -67,18 +68,18 @@ func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Cal
}
// 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()
ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// 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()
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

View File

@ -223,9 +223,18 @@ func (s sdk) DeleteStack(ctx context.Context, name string) error {
return err
}
func (s sdk) CreateSecret(ctx context.Context, name string, secret string) (string, error) {
logrus.Debug("Create secret " + name)
response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{Name: &name, SecretString: &secret})
func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
logrus.Debug("Create secret " + secret.Name)
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 {
return "", err
}

View File

@ -7,14 +7,14 @@ import (
)
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)
ListSecrets(ctx context.Context) ([]docker.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error
}
func (c client) CreateSecret(ctx context.Context, name string, content string) (string, error) {
return c.api.CreateSecret(ctx, name, content)
func (c client) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
return c.api.CreateSecret(ctx, secret)
}
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
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)
ListSecrets(ctx context.Context) ([]docker.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error

View File

@ -9,6 +9,17 @@ type Secret struct {
Name string `json:"Name"`
Labels map[string]string `json:"Labels"`
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) {
@ -18,3 +29,15 @@ func (s Secret) ToJSON() (string, error) {
}
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
}