// Use the Rack Settings bucket and EncryptionKey KMS key to store and retrieve // sensitive credentials, just like app env func GetRackSettings() (Environment, error) { key := os.Getenv("ENCRYPTION_KEY") settings := os.Getenv("SETTINGS_BUCKET") data, err := s3Get(settings, "env") if err != nil { // if we get a 404 from aws just return an empty environment if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 { return Environment{}, nil } return nil, err } if key != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) if d, err := cr.Decrypt(key, data); err == nil { data = d } } var env Environment err = json.Unmarshal(data, &env) if err != nil { return nil, err } return env, nil }
func GetEnvironment(app string) (Environment, error) { a, err := GetApp(app) if err != nil { return nil, err } data, err := s3Get(a.Outputs["Settings"], "env") if err != nil { // if we get a 404 from aws just return an empty environment if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 { return Environment{}, nil } return nil, err } if a.Parameters["Key"] != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) if d, err := cr.Decrypt(a.Parameters["Key"], data); err == nil { data = d } } return LoadEnvironment(data), nil }
func PutRackSettings(env Environment) error { a, err := GetApp(os.Getenv("RACK")) if err != nil { return err } resources, err := ListResources(a.Name) if err != nil { return err } key := resources["EncryptionKey"].Id settings := resources["Settings"].Id e, err := json.Marshal(env) if err != nil { return err } if key != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) e, err = cr.Encrypt(key, e) if err != nil { return err } } err = S3Put(settings, "env", []byte(e), true) return err }
func PutEnvironment(app string, env Environment) error { a, err := GetApp(app) if err != nil { return err } release, err := a.ForkRelease() if err != nil { return err } release.Env = env.Raw() err = release.Save() if err != nil { return err } e := []byte(env.Raw()) if a.Parameters["Key"] != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) e, err = cr.Encrypt(a.Parameters["Key"], e) if err != nil { return err } } return S3Put(a.Outputs["Settings"], "env", []byte(e), true) }
func (p *AWSProvider) EnvironmentGet(app string) (structs.Environment, error) { a, err := p.AppGet(app) if err != nil { return nil, err } if a.Status == "creating" { return nil, fmt.Errorf("app is still being created: %s", app) } data, err := p.s3Get(a.Outputs["Settings"], "env") if err != nil { // if we get a 404 from aws just return an empty environment if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 { return structs.Environment{}, nil } return nil, err } if a.Parameters["Key"] != "" { cr := crypt.New(p.Region, p.Access, p.Secret) if d, err := cr.Decrypt(a.Parameters["Key"], data); err == nil { data = d } } env := structs.Environment{} env.LoadEnvironment(data) return env, nil }
func (r *Release) Save() error { if r.Id == "" { return fmt.Errorf("Id must not be blank") } if r.Created.IsZero() { r.Created = time.Now() } req := &dynamodb.PutItemInput{ Item: map[string]*dynamodb.AttributeValue{ "id": &dynamodb.AttributeValue{S: aws.String(r.Id)}, "app": &dynamodb.AttributeValue{S: aws.String(r.App)}, "created": &dynamodb.AttributeValue{S: aws.String(r.Created.Format(SortableTime))}, }, TableName: aws.String(releasesTable(r.App)), } if r.Build != "" { req.Item["build"] = &dynamodb.AttributeValue{S: aws.String(r.Build)} } if r.Env != "" { req.Item["env"] = &dynamodb.AttributeValue{S: aws.String(r.Env)} } if r.Manifest != "" { req.Item["manifest"] = &dynamodb.AttributeValue{S: aws.String(r.Manifest)} } _, err := DynamoDB().PutItem(req) if err != nil { return err } app, err := GetApp(r.App) if err != nil { return err } env := []byte(r.Env) if app.Parameters["Key"] != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) env, err = cr.Encrypt(app.Parameters["Key"], []byte(env)) if err != nil { return err } } NotifySuccess("release:create", map[string]string{"id": r.Id, "app": r.App}) return S3Put(app.Outputs["Settings"], fmt.Sprintf("releases/%s/env", r.Id), env, true) }
func (p *AWSProvider) ReleaseSave(r *structs.Release, bucket, key string) error { if r.Id == "" { return fmt.Errorf("Id can not be blank") } if r.Created.IsZero() { r.Created = time.Now() } req := &dynamodb.PutItemInput{ Item: map[string]*dynamodb.AttributeValue{ "id": &dynamodb.AttributeValue{S: aws.String(r.Id)}, "app": &dynamodb.AttributeValue{S: aws.String(r.App)}, "created": &dynamodb.AttributeValue{S: aws.String(r.Created.Format(SortableTime))}, }, TableName: aws.String(releasesTable(r.App)), } if r.Build != "" { req.Item["build"] = &dynamodb.AttributeValue{S: aws.String(r.Build)} } if r.Env != "" { req.Item["env"] = &dynamodb.AttributeValue{S: aws.String(r.Env)} } if r.Manifest != "" { req.Item["manifest"] = &dynamodb.AttributeValue{S: aws.String(r.Manifest)} } var err error env := []byte(r.Env) if key != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) env, err = cr.Encrypt(key, []byte(env)) if err != nil { return err } } _, err = p.s3().PutObject(&s3.PutObjectInput{ ACL: aws.String("public-read"), Body: bytes.NewReader(env), Bucket: aws.String(bucket), ContentLength: aws.Int64(int64(len(env))), Key: aws.String(fmt.Sprintf("releases/%s/env", r.Id)), }) if err != nil { return err } _, err = p.dynamodb().PutItem(req) return err }
func buildCrypt(c *cli.Context) (*crypt.Crypt, error) { if role := c.GlobalString("role"); role != "" { return crypt.NewIam(role) } else { region := c.GlobalString("region") access := c.GlobalString("access") secret := c.GlobalString("secret") return crypt.New(region, access, secret), nil } }
func PutEnvironment(app string, env Environment) (string, error) { a, err := GetApp(app) if err != nil { return "", err } switch a.Status { case "creating": return "", fmt.Errorf("app is still creating: %s", app) case "running", "updating": default: return "", fmt.Errorf("unable to set environment on app: %s", app) } release, err := a.ForkRelease() if err != nil { return "", err } release.Env = env.Raw() err = release.Save() if err != nil { return "", err } e := []byte(env.Raw()) if a.Parameters["Key"] != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) e, err = cr.Encrypt(a.Parameters["Key"], e) if err != nil { return "", err } } err = S3Put(a.Outputs["Settings"], "env", []byte(e), true) if err != nil { return "", err } return release.Id, nil }
// Use the Rack Settings bucket and EncryptionKey KMS key to store and retrieve // sensitive credentials, just like app env func GetRackSettings() (Environment, error) { a, err := GetApp(os.Getenv("RACK")) if err != nil { return nil, err } resources, err := ListResources(a.Name) if err != nil { return nil, err } key := resources["EncryptionKey"].Id settings := resources["Settings"].Id data, err := s3Get(settings, "env") if err != nil { // if we get a 404 from aws just return an empty environment if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 { return Environment{}, nil } return nil, err } if key != "" { cr := crypt.New(os.Getenv("AWS_REGION"), os.Getenv("AWS_ACCESS"), os.Getenv("AWS_SECRET")) if d, err := cr.Decrypt(key, data); err == nil { data = d } } var env Environment err = json.Unmarshal(data, &env) if err != nil { return nil, err } return env, nil }
func ECSTaskDefinitionCreate(req Request) (string, map[string]string, error) { // return "", fmt.Errorf("fail") tasks := req.ResourceProperties["Tasks"].([]interface{}) r := &ecs.RegisterTaskDefinitionInput{ Family: aws.String(req.ResourceProperties["Name"].(string)), } // get environment from S3 URL // 'Environment' is a CloudFormation Template Property that references 'Environment' CF Parameter with S3 URL // S3 body may be encrypted with KMS key var env models.Environment if envUrl, ok := req.ResourceProperties["Environment"].(string); ok && envUrl != "" { res, err := http.Get(envUrl) if err != nil { return "invalid", nil, err } defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if key, ok := req.ResourceProperties["Key"].(string); ok && key != "" { cr := crypt.New(*Region(&req), os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY")) cr.AwsToken = os.Getenv("AWS_SESSION_TOKEN") dec, err := cr.Decrypt(key, data) if err != nil { return "invalid", nil, err } data = dec } env = models.LoadEnvironment(data) } r.ContainerDefinitions = make([]*ecs.ContainerDefinition, len(tasks)) for i, itask := range tasks { task := itask.(map[string]interface{}) cpu := 0 var err error if c, ok := task["Cpu"].(string); ok && c != "" { cpu, err = strconv.Atoi(c) if err != nil { return "invalid", nil, err } } memory, err := strconv.Atoi(task["Memory"].(string)) if err != nil { return "invalid", nil, err } privileged := false if p, ok := task["Privileged"].(string); ok && p != "" { privileged, err = strconv.ParseBool(p) if err != nil { return "invalid", nil, err } } r.ContainerDefinitions[i] = &ecs.ContainerDefinition{ Name: aws.String(task["Name"].(string)), Essential: aws.Bool(true), Image: aws.String(task["Image"].(string)), Cpu: aws.Int64(int64(cpu)), Memory: aws.Int64(int64(memory)), Privileged: aws.Bool(privileged), } // set Command from either - // a single string (shell form) - ["sh", "-c", command] // an array of strings (exec form) - ["cmd1", "cmd2"] switch commands := task["Command"].(type) { case string: if commands != "" { r.ContainerDefinitions[i].Command = []*string{aws.String("sh"), aws.String("-c"), aws.String(commands)} } case []interface{}: r.ContainerDefinitions[i].Command = make([]*string, len(commands)) for j, command := range commands { r.ContainerDefinitions[i].Command[j] = aws.String(command.(string)) } } // set Task environment from CF Tasks[].Environment key/values // These key/values are read from the app manifest environment hash if oenv, ok := task["Environment"].(map[string]interface{}); ok { for key, val := range oenv { r.ContainerDefinitions[i].Environment = append(r.ContainerDefinitions[i].Environment, &ecs.KeyValuePair{ Name: aws.String(key), Value: aws.String(val.(string)), }) } } // set Task environment from decrypted S3 URL body of key/values // These key/values take precident over the above environment for key, val := range env { r.ContainerDefinitions[i].Environment = append(r.ContainerDefinitions[i].Environment, &ecs.KeyValuePair{ Name: aws.String(key), Value: aws.String(val), }) } // set Release value in Task environment if release, ok := req.ResourceProperties["Release"].(string); ok { r.ContainerDefinitions[i].Environment = append(r.ContainerDefinitions[i].Environment, &ecs.KeyValuePair{ Name: aws.String("RELEASE"), Value: aws.String(release), }) } // set links if links, ok := task["Links"].([]interface{}); ok { r.ContainerDefinitions[i].Links = make([]*string, len(links)) for j, link := range links { r.ContainerDefinitions[i].Links[j] = aws.String(link.(string)) } } // set portmappings if ports, ok := task["PortMappings"].([]interface{}); ok { r.ContainerDefinitions[i].PortMappings = make([]*ecs.PortMapping, len(ports)) for j, port := range ports { parts := strings.Split(port.(string), ":") host, _ := strconv.Atoi(parts[0]) container, _ := strconv.Atoi(parts[1]) r.ContainerDefinitions[i].PortMappings[j] = &ecs.PortMapping{ ContainerPort: aws.Int64(int64(container)), HostPort: aws.Int64(int64(host)), } } } // set volumes if volumes, ok := task["Volumes"].([]interface{}); ok { for j, volume := range volumes { name := fmt.Sprintf("%s-%d-%d", task["Name"].(string), i, j) parts := strings.Split(volume.(string), ":") r.Volumes = append(r.Volumes, &ecs.Volume{ Name: aws.String(name), Host: &ecs.HostVolumeProperties{ SourcePath: aws.String(parts[0]), }, }) r.ContainerDefinitions[i].MountPoints = append(r.ContainerDefinitions[i].MountPoints, &ecs.MountPoint{ SourceVolume: aws.String(name), ContainerPath: aws.String(parts[1]), ReadOnly: aws.Bool(false), }) } } // set extra hosts if extraHosts, ok := task["ExtraHosts"].([]interface{}); ok { for _, host := range extraHosts { hostx, oky := host.(map[string]interface{}) if oky { r.ContainerDefinitions[i].ExtraHosts = append(r.ContainerDefinitions[i].ExtraHosts, &ecs.HostEntry{ Hostname: aws.String(hostx["HostName"].(string)), IpAddress: aws.String(hostx["IpAddress"].(string)), }) } } } } res, err := ECS(req).RegisterTaskDefinition(r) if err != nil { return "invalid", nil, err } return *res.TaskDefinition.TaskDefinitionArn, nil, nil }
func (p *AWSProvider) migrateClassicAuth() error { r, err := p.ObjectFetch("env") if err != nil && !ErrorNotFound(err) { return err } data := []byte("{}") if r != nil { defer r.Close() d, err := ioutil.ReadAll(r) if err != nil { return err } data = d if p.EncryptionKey != "" { cr := crypt.New(p.Region, p.Access, p.Secret) if d, err := cr.Decrypt(p.EncryptionKey, data); err == nil { data = d } } } var env map[string]string err = json.Unmarshal(data, &env) if err != nil { return err } type authEntry struct { Username string Password string } auth := map[string]authEntry{} if ea, ok := env["DOCKER_AUTH_DATA"]; ok { if err := json.Unmarshal([]byte(ea), &auth); err != nil { return err } } if len(auth) == 0 { return nil } for server, entry := range auth { if _, err := p.RegistryAdd(server, entry.Username, entry.Password); err != nil { return err } } delete(env, "DOCKER_AUTH_DATA") data, err = json.Marshal(env) if err != nil { return err } _, err = p.ObjectStore("env", bytes.NewReader(data), structs.ObjectOptions{Public: false}) if err != nil { return err } return nil }
// ReleaseSave saves a Release func (p *AWSProvider) ReleaseSave(r *structs.Release) error { a, err := p.AppGet(r.App) if err != nil { return err } bucket := a.Outputs["Settings"] key := a.Parameters["Key"] if r.Id == "" { return fmt.Errorf("Id can not be blank") } if r.Created.IsZero() { r.Created = time.Now() } if p.IsTest() { r.Created = time.Unix(1473028693, 0).UTC() } req := &dynamodb.PutItemInput{ Item: map[string]*dynamodb.AttributeValue{ "id": {S: aws.String(r.Id)}, "app": {S: aws.String(r.App)}, "created": {S: aws.String(r.Created.Format(sortableTime))}, }, TableName: aws.String(p.DynamoReleases), } if r.Build != "" { req.Item["build"] = &dynamodb.AttributeValue{S: aws.String(r.Build)} } if r.Env != "" { req.Item["env"] = &dynamodb.AttributeValue{S: aws.String(r.Env)} } if r.Manifest != "" { req.Item["manifest"] = &dynamodb.AttributeValue{S: aws.String(r.Manifest)} } env := []byte(r.Env) if key != "" { cr := crypt.New(p.Region, p.Access, p.Secret) env, err = cr.Encrypt(key, []byte(env)) if err != nil { return err } } _, err = p.s3().PutObject(&s3.PutObjectInput{ ACL: aws.String("public-read"), Body: bytes.NewReader(env), Bucket: aws.String(bucket), ContentLength: aws.Int64(int64(len(env))), Key: aws.String(fmt.Sprintf("releases/%s/env", r.Id)), }) if err != nil { return err } _, err = p.dynamodb().PutItem(req) return err }