func FileConfig() Config { config_file := utils.DefaultIfBlank(os.Getenv("ECS_AGENT_CONFIG_FILE_PATH"), "/etc/ecs_container_agent/config.json") file, err := os.Open(config_file) if err != nil { return Config{} } data, err := ioutil.ReadAll(file) if err != nil { log.Error("Unable to read config file", "err", err) return Config{} } if strings.TrimSpace(string(data)) == "" { // empty file, not an error return Config{} } config := Config{} err = json.Unmarshal(data, &config) if err != nil { log.Error("Error reading config json data", "err", err) } // Handle any deprecated keys correctly here if utils.ZeroOrNil(config.Cluster) && !utils.ZeroOrNil(config.ClusterArn) { config.Cluster = config.ClusterArn } return config }
// SearchStrInDir searches the files in direcotry for specific content func SearchStrInDir(dir, filePrefix, content string) error { logfiles, err := ioutil.ReadDir(dir) if err != nil { return fmt.Errorf("Error reading the directory, err %v", err) } var desiredFile string for _, file := range logfiles { if strings.HasPrefix(file.Name(), filePrefix) { desiredFile = file.Name() break } } if utils.ZeroOrNil(desiredFile) { return fmt.Errorf("File with prefix: %v does not exist", filePrefix) } data, err := ioutil.ReadFile(filepath.Join(dir, desiredFile)) if err != nil { return fmt.Errorf("Failed to read file, err: %v", err) } if !strings.Contains(string(data), content) { return fmt.Errorf("Could not find the content: %v in the file: %v", content, desiredFile) } return nil }
// checkMissingAndDeprecated checks all zero-valued fields for tags of the form // missing:STRING and acts based on that string. Current options are: fatal, // warn. Fatal will result in an error being returned, warn will result in a // warning that the field is missing being logged. func (cfg *Config) checkMissingAndDepreciated() error { cfgElem := reflect.ValueOf(cfg).Elem() cfgStructField := reflect.Indirect(reflect.ValueOf(cfg)).Type() fatalFields := []string{} for i := 0; i < cfgElem.NumField(); i++ { cfgField := cfgElem.Field(i) if utils.ZeroOrNil(cfgField.Interface()) { missingTag := cfgStructField.Field(i).Tag.Get("missing") if len(missingTag) == 0 { continue } switch missingTag { case "warn": log.Warn("Configuration key not set", "key", cfgStructField.Field(i).Name) case "fatal": log.Crit("Configuration key not set", "key", cfgStructField.Field(i).Name) fatalFields = append(fatalFields, cfgStructField.Field(i).Name) default: log.Warn("Unexpected `missing` tag value", "tag", missingTag) } } else { // present deprecatedTag := cfgStructField.Field(i).Tag.Get("deprecated") if len(deprecatedTag) == 0 { continue } log.Warn("Use of deprecated configuration key", "key", cfgStructField.Field(i).Name, "message", deprecatedTag) } } if len(fatalFields) > 0 { return errors.New("Missing required fields: " + strings.Join(fatalFields, ", ")) } return nil }
// complete returns true if all fields of the config are populated / nonzero func (cfg *Config) complete() bool { cfgElem := reflect.ValueOf(cfg).Elem() for i := 0; i < cfgElem.NumField(); i++ { if utils.ZeroOrNil(cfgElem.Field(i).Interface()) { return false } } return true }
// GetInstanceIAMRole gets the iam roles attached to the instance profile func GetInstanceIAMRole() ([]*iam.Role, error) { instanceProfileName, err := GetInstanceMetadata("iam/security-credentials") if err != nil { return nil, fmt.Errorf("Error getting instance profile name, err: %v", err) } if utils.ZeroOrNil(instanceProfileName) { return nil, fmt.Errorf("Instance Profile name nil") } iamClient := iam.New(session.New()) instanceProfile, err := iamClient.GetInstanceProfile(&iam.GetInstanceProfileInput{ InstanceProfileName: aws.String(instanceProfileName), }) if err != nil { return nil, err } if instanceProfile.InstanceProfile == nil || utils.ZeroOrNil(instanceProfile.InstanceProfile.Roles) { return nil, fmt.Errorf("No roles found") } return instanceProfile.InstanceProfile.Roles, nil }
// Merge merges two config files, preferring the ones on the left. Any nil or // zero values present in the left that are not present in the right will be // overridden func (lhs *Config) Merge(rhs Config) *Config { left := reflect.ValueOf(lhs).Elem() right := reflect.ValueOf(&rhs).Elem() for i := 0; i < left.NumField(); i++ { leftField := left.Field(i) if utils.ZeroOrNil(leftField.Interface()) { leftField.Set(reflect.ValueOf(right.Field(i).Interface())) } } return lhs //make it chainable }
// GetInstanceIAMRole gets the iam roles attached to the instance profile func GetInstanceIAMRole() (*iam.Role, error) { // This returns the name of the role instanceRoleName, err := GetInstanceMetadata("iam/security-credentials") if err != nil { return nil, fmt.Errorf("Error getting instance role name, err: %v", err) } if utils.ZeroOrNil(instanceRoleName) { return nil, fmt.Errorf("Instance Role name nil") } iamClient := iam.New(session.New()) instanceRole, err := iamClient.GetRole(&iam.GetRoleInput{ RoleName: aws.String(instanceRoleName), }) if err != nil { return nil, err } return instanceRole.Role, nil }
func TestTaskIamRoles(t *testing.T) { // The test runs only when the environment TEST_IAM_ROLE was set if os.Getenv("TEST_TASK_IAM_ROLE") != "true" { t.Skip("Skipping test TaskIamRole, as TEST_IAM_ROLE isn't set true") } roleArn := os.Getenv("TASK_IAM_ROLE_ARN") if utils.ZeroOrNil(roleArn) { t.Logf("TASK_IAM_ROLE_ARN not set, will try to use the role attached to instance profile") roles, err := GetInstanceIAMRole() if err != nil { t.Fatalf("Error getting IAM Roles from instance profile, err: %v", err) } roleArn = *roles[0].Arn } agentOptions := &AgentOptions{ ExtraEnvironment: map[string]string{ "ECS_ENABLE_TASK_IAM_ROLE": "true", }, PortBindings: map[docker.Port]map[string]string{ "51679/tcp": map[string]string{ "HostIP": "0.0.0.0", "HostPort": "51679", }, }, } agent := RunAgent(t, agentOptions) defer agent.Cleanup() tdOverride := make(map[string]string) tdOverride["$$$TASK_ROLE$$$"] = roleArn tdOverride["$$$TEST_REGION$$$"] = *ECS.Config.Region task, err := agent.StartTaskWithTaskDefinitionOverrides(t, "iam-roles", tdOverride) if err != nil { t.Fatalf("Error start iam-roles task: %v", err) } err = task.WaitRunning(waitTaskStateChangeDuration) if err != nil { t.Fatalf("Error waiting for task to run: %v", err) } containerId, err := agent.ResolveTaskDockerID(task, "container-with-iamrole") if err != nil { t.Fatalf("Error resolving docker id for container in task: %v", err) } // TaskIAMRoles enabled contaienr should have the ExtraEnvironment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI containerMetaData, err := agent.DockerClient.InspectContainer(containerId) if err != nil { t.Fatalf("Could not inspect container for task: %v", err) } iamRoleEnabled := false if containerMetaData.Config != nil { for _, env := range containerMetaData.Config.Env { if strings.HasPrefix(env, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=") { iamRoleEnabled = true break } } } if !iamRoleEnabled { task.Stop() t.Fatalf("Could not found AWS_CONTAINER_CREDENTIALS_RELATIVE_URI in the container envrionment variable") } // Task will only run one command "aws ec2 describe-regions" err = task.WaitStopped(30 * time.Second) if err != nil { t.Fatalf("Waiting task to stop error : %v", err) } containerMetaData, err = agent.DockerClient.InspectContainer(containerId) if err != nil { t.Fatalf("Could not inspect container for task: %v", err) } if containerMetaData.State.ExitCode != 0 { t.Fatalf("Container exit code non-zero: %v", containerMetaData.State.ExitCode) } // Search the audit log to verify the credential request err = SearchStrInDir(filepath.Join(agent.TestDir, "log"), "audit.log.", *task.TaskArn) if err != nil { t.Fatalf("Verify credential request failed, err: %v", err) } }
func taskIamRolesTest(networkMode string, agent *TestAgent, t *testing.T) { RequireDockerVersion(t, ">=1.11.0") // TaskIamRole is available from agent 1.11.0 roleArn := os.Getenv("TASK_IAM_ROLE_ARN") if utils.ZeroOrNil(roleArn) { t.Logf("TASK_IAM_ROLE_ARN not set, will try to use the role attached to instance profile") role, err := GetInstanceIAMRole() if err != nil { t.Fatalf("Error getting IAM Roles from instance profile, err: %v", err) } roleArn = *role.Arn } tdOverride := make(map[string]string) tdOverride["$$$TASK_ROLE$$$"] = roleArn tdOverride["$$$TEST_REGION$$$"] = *ECS.Config.Region tdOverride["$$$NETWORK_MODE$$$"] = networkMode task, err := agent.StartTaskWithTaskDefinitionOverrides(t, "iam-roles", tdOverride) if err != nil { t.Fatalf("Error start iam-roles task: %v", err) } err = task.WaitRunning(waitTaskStateChangeDuration) if err != nil { t.Fatalf("Error waiting for task to run: %v", err) } containerId, err := agent.ResolveTaskDockerID(task, "container-with-iamrole") if err != nil { t.Fatalf("Error resolving docker id for container in task: %v", err) } // TaskIAMRoles enabled contaienr should have the ExtraEnvironment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI containerMetaData, err := agent.DockerClient.InspectContainer(containerId) if err != nil { t.Fatalf("Could not inspect container for task: %v", err) } iamRoleEnabled := false if containerMetaData.Config != nil { for _, env := range containerMetaData.Config.Env { if strings.HasPrefix(env, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=") { iamRoleEnabled = true break } } } if !iamRoleEnabled { task.Stop() t.Fatalf("Could not found AWS_CONTAINER_CREDENTIALS_RELATIVE_URI in the container envrionment variable") } // Task will only run one command "aws ec2 describe-regions" err = task.WaitStopped(30 * time.Second) if err != nil { t.Fatalf("Waiting task to stop error : %v", err) } containerMetaData, err = agent.DockerClient.InspectContainer(containerId) if err != nil { t.Fatalf("Could not inspect container for task: %v", err) } if containerMetaData.State.ExitCode != 0 { t.Fatalf("Container exit code non-zero: %v", containerMetaData.State.ExitCode) } // Search the audit log to verify the credential request err = SearchStrInDir(filepath.Join(agent.TestDir, "log"), "audit.log.", *task.TaskArn) if err != nil { t.Fatalf("Verify credential request failed, err: %v", err) } }