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

Add AWS Secret manager integration to Container Definition #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.amazonaws.services.ecs.model.ContainerDefinition;
import com.amazonaws.services.ecs.model.KeyValuePair;
import com.amazonaws.services.ecs.model.Secret;
import com.thoughtworks.gocd.elasticagent.ecs.domain.ElasticAgentProfileProperties;
import com.thoughtworks.gocd.elasticagent.ecs.domain.PluginSettings;
import com.thoughtworks.gocd.elasticagent.ecs.requests.CreateAgentRequest;
Expand All @@ -26,12 +27,14 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import static com.thoughtworks.gocd.elasticagent.ecs.Constants.*;
import static com.thoughtworks.gocd.elasticagent.ecs.ECSElasticPlugin.LOG;
import static com.thoughtworks.gocd.elasticagent.ecs.domain.Platform.LINUX;
import static com.thoughtworks.gocd.elasticagent.ecs.domain.Platform.WINDOWS;
import static java.text.MessageFormat.format;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

public class ContainerDefinitionBuilder {
private String taskName;
Expand All @@ -48,6 +51,7 @@ public ContainerDefinition build() {
.withMemory(elasticAgentProfileProperties.getMaxMemory())
.withMemoryReservation(memoryReservation)
.withEnvironment(environmentFrom())
.withSecrets(secretsFrom(elasticAgentProfileProperties.getSecretName(), elasticAgentProfileProperties.getSecretValue()))
.withDockerLabels(labelsFrom())
.withCommand(elasticAgentProfileProperties.getCommand())
.withCpu(elasticAgentProfileProperties.getCpu())
Expand Down Expand Up @@ -75,6 +79,15 @@ private Collection<KeyValuePair> environmentFrom() {
return env;
}

private Collection<Secret> secretsFrom(String secretName, String secretValue) {
Collection<Secret> secrets = new HashSet<>();
if (isNotBlank(secretName) && isNotBlank(secretValue)) {
secrets.add(new Secret().withName(secretName).withValueFrom(secretValue));
LOG.info(format("Added new secret to container definition: {0} - {1}", secretName, secretValue));
}
return secrets;
}

private Collection<KeyValuePair> getKeyValuePairs(Collection<String> lines) {
Collection<KeyValuePair> env = new HashSet<>();
for (String variable : lines) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public RegisterTaskDefinitionRequest build(PluginSettings pluginSettings, Elasti
});
}

if (isNotBlank(elasticAgentProfileProperties.getExecutionRoleArn())) {
LOG.info(format("[create-agent] Adding execution Role to task configuration: {0}", elasticAgentProfileProperties.getExecutionRoleArn()));
request.withExecutionRoleArn(elasticAgentProfileProperties.getExecutionRoleArn());
}

return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public class ElasticAgentProfileProperties {
public static final String TASK_ROLE_ARN = "TaskRoleArn";
public static final String SECURITY_GROUP_IDS = "SecurityGroupIds";
public static final String SUBNET_IDS = "SubnetIds";
public static final String SECRET_NAME = "SecretName";
public static final String SECRET_VALUE = "SecretValue";
public static final String EXECUTION_ROLE_ARN = "ExecutionRoleArn";
public static final String PLATFORM = "Platform";
public static final String BIND_MOUNT = "BindMount";
public static final String RUN_AS_SPOT_INSTANCE = "RunAsSpotInstance";
Expand Down Expand Up @@ -114,6 +117,21 @@ public class ElasticAgentProfileProperties {
@Metadata(key = SUBNET_IDS, required = false, secure = false)
private String subnetIds;

@Expose
@SerializedName(SECRET_NAME)
@Metadata(key = SECRET_NAME, required = false, secure = false)
private String secretName;

@Expose
@SerializedName(SECRET_VALUE)
@Metadata(key = SECRET_VALUE, required = false, secure = false)
private String secretValue;

@Expose
@SerializedName(EXECUTION_ROLE_ARN)
@Metadata(key = EXECUTION_ROLE_ARN, required = false, secure = false)
private String executionRoleArn;

@Expose
@SerializedName(SECURITY_GROUP_IDS)
@Metadata(key = SECURITY_GROUP_IDS, required = false, secure = false)
Expand Down Expand Up @@ -193,6 +211,18 @@ public List<String> getSubnetIds() {
return listFromCommaSeparatedString(subnetIds);
}

public String getSecretName() {
return stripToEmpty(secretName);
}

public String getSecretValue() {
return stripToEmpty(secretValue);
}

public String getExecutionRoleArn() {
return executionRoleArn;
}

public List<String> getSecurityGroupIds() {
return listFromCommaSeparatedString(securityGroupIds);
}
Expand Down
39 changes: 39 additions & 0 deletions src/main/resources/profile.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,45 @@
</div>
</div>

<div>
<label ng-class="{'is-invalid-label': GOINPUTNAME[SecretName].$error.server}">
Secret name
</label>
<input ng-class="{'is-invalid-input': GOINPUTNAME[SecretName].$error.server}" type="text" ng-model="SecretName"
ng-required="true" placeholder="e.g. subnet-xyz, subnet-abc"/>
<span class="form_error form-error" ng-class="{'is-visible': GOINPUTNAME[SecretName].$error.server}"
ng-show="GOINPUTNAME[SecretName].$error.server">{{GOINPUTNAME[SecretName].$error.server}}</span>
<div class="form-help-content-one-line">
The environment variable where the Secret value will be assigned to and accesible from the ECS task
</div>
</div>

<div>
<label ng-class="{'is-invalid-label': GOINPUTNAME[SecretValue].$error.server}">
Secret value (ARN)
</label>
<input ng-class="{'is-invalid-input': GOINPUTNAME[SecretValue].$error.server}" type="text" ng-model="SecretValue"
ng-required="true" placeholder="e.g. secret name"/>
<span class="form_error form-error" ng-class="{'is-visible': GOINPUTNAME[SecretValue].$error.server}"
ng-show="GOINPUTNAME[SecretValue].$error.server">{{GOINPUTNAME[SecretValue].$error.server}}</span>
<div class="form-help-content-one-line">
The secret to expose to the container. The supported values are either the full ARN of the AWS Secrets Manager secret or the full ARN of the parameter in the AWS Systems Manager Parameter Store.
</div>
</div>

<div>
<label ng-class="{'is-invalid-label': GOINPUTNAME[ExecutionRoleArn].$error.server}">
Execution Role Arn
</label>
<input ng-class="{'is-invalid-input': GOINPUTNAME[ExecutionRoleArn].$error.server}" type="text" ng-model="ExecutionRoleArn"
ng-required="true" placeholder="e.g. arn:aws:iam::account-id:role/executionRoleName"/>
<span class="form_error form-error" ng-class="{'is-visible': GOINPUTNAME[ExecutionRoleArn].$error.server}"
ng-show="GOINPUTNAME[ExecutionRoleArn].$error.server">{{GOINPUTNAME[ExecutionRoleArn].$error.server}}</span>
<div class="form-help-content-one-line">
The Amazon Resource Name (ARN) of the task execution role that grants the Amazon ECS container agent permission to make AWS API calls on your behalf.
</div>
</div>

<div class="form_item_block">
<input type="checkbox" ng-model="RunAsSpotInstance" ng-required="false" ng-true-value="true"
ng-false-value="false"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ void shouldCreateValidMetadata() throws Exception {
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecretName\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecretValue\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"ExecutionRoleArn\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecurityGroupIds\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
Expand Down Expand Up @@ -178,6 +199,9 @@ void shouldParseJsonToElasticProfile() {
" \"AMI\": \"ami-123456\",\n" +
" \"InstanceType\": \"t2.small\",\n" +
" \"SubnetIds\": \"subnet-abc045we\",\n" +
" \"SecretName\": \"secret-name\",\n" +
" \"SecretValue\": \"secret-value\",\n" +
" \"ExecutionRoleArn\": \"executionRoleArn\",\n" +
" \"SecurityGroupIds\": \"sg-ec33sl0,sg-ec33sl2,sg-ec33sl1\",\n" +
" \"EC2TerminateAfter\": \"240\",\n" +
" \"IAMInstanceProfile\": \"ecsInstanceRole\",\n" +
Expand All @@ -197,6 +221,9 @@ void shouldParseJsonToElasticProfile() {
assertThat(elasticAgentProfileProperties.getAmiID()).isEqualTo("ami-123456");
assertThat(elasticAgentProfileProperties.getInstanceType()).isEqualTo("t2.small");
assertThat(elasticAgentProfileProperties.getSubnetIds()).contains("subnet-abc045we");
assertThat(elasticAgentProfileProperties.getSecretName()).isEqualTo("secret-name");
assertThat(elasticAgentProfileProperties.getSecretValue()).isEqualTo("secret-value");
assertThat(elasticAgentProfileProperties.getExecutionRoleArn()).isEqualTo("executionRoleArn");
assertThat(elasticAgentProfileProperties.getSecurityGroupIds()).contains("sg-ec33sl0", "sg-ec33sl2", "sg-ec33sl1");
assertThat(elasticAgentProfileProperties.getEC2IamInstanceProfile()).isEqualTo("ecsInstanceRole");
assertThat(elasticAgentProfileProperties.isPrivileged()).isEqualTo(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@ void assertJsonStructure() throws Exception {
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecretName\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecretValue\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"ExecutionRoleArn\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
" \"secure\": false\n" +
" }\n" +
" },\n" +
" {\n" +
" \"key\": \"SecurityGroupIds\",\n" +
" \"metadata\": {\n" +
" \"required\": false,\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ static Stream<Arguments> fieldArguments() {
Arguments.of("InstanceType", "Instance type"),
Arguments.of("IAMInstanceProfile", "IAM Instance Profile"),
Arguments.of("SecurityGroupIds", "Security Group Id(s)"),
Arguments.of("SubnetIds", "Subnet id(s)")
Arguments.of("SubnetIds", "Subnet id(s)"),
Arguments.of("SecretName", "Secret name"),
Arguments.of("SecretValue", "Secret value"),
Arguments.of("ExecutionRoleArn", "Execution Role Arn")
);
}

Expand Down