├── local └── config │ ├── env.properties │ └── service.properties ├── .gitignore ├── Dockerfile ├── CONTRIBUTING ├── NOTICE ├── monitors ├── log4j2-syslog.xml └── log4j2.xml ├── src ├── main │ └── java │ │ └── com │ │ └── secretsagent │ │ ├── Main.java │ │ ├── SecretsManager.java │ │ └── SecretsProcessor.java └── test │ └── java │ └── com │ └── secretsagent │ ├── SecretsManagerTest.java │ └── SecretsProcessorTest.java ├── start.sh ├── run.sh ├── README.md ├── pom.xml └── LICENSE /local/config/env.properties: -------------------------------------------------------------------------------- 1 | LOG_LEVEL=INFO 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | target/ 3 | local/secrets 4 | *.iml 5 | .idea/ 6 | .DS_Store 7 | ._* 8 | -------------------------------------------------------------------------------- /local/config/service.properties: -------------------------------------------------------------------------------- 1 | secret.name=valws/lab.main.t.aor.valws.vaw2 2 | region=us-east-1 3 | secrets.filename=/secrets/secrets.properties 4 | is.local.run=true -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-alpine 2 | ARG JAR_FILE 3 | COPY /target/${JAR_FILE} /app/agent/${JAR_FILE} 4 | COPY /target/dependency /app/agent 5 | COPY /monitors /app/monitors 6 | COPY start.sh /app/bin/start.sh 7 | RUN chmod +rwx /app/bin/start.sh 8 | ENTRYPOINT ["/app/bin/start.sh"] -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | If you would like to contribute code to this project you can do so through 2 | GitHub by forking the repository and sending a pull request. 3 | 4 | Before Comcast merges your code into the project you must sign the [Comcast Contributor License Agreement (CLA)](https://gist.github.com/ComcastOSS/a7b8933dd8e368535378cda25c92d19a). 5 | 6 | If you haven't previously signed a Comcast CLA, you'll automatically be asked to when you open a pull request. 7 | Alternatively, we can send you a PDF that you can sign and scan back to us. 8 | Please create a new GitHub issue to request a PDF version of the CLA. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Comcast Cable Communications Management, LLC 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | This product includes software developed at Comcast (http://www.comcast.com/). 16 | -------------------------------------------------------------------------------- /monitors/log4j2-syslog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | awssecretsagent {"index": "service", "ccds_cluster_title": "${clusterTitle}", "type": "awssecrets-agent", "event": "%d{ISO8601_OFFSET_DATE_TIME_HH} %X{CID:--} %-5p [%t] %c: %m%n"} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/com/secretsagent/Main.java: -------------------------------------------------------------------------------- 1 | package com.secretsagent; 2 | 3 | /** 4 | * Copyright 2020 Comcast Cable Communications Management, LLC 5 | * 6 | * Licensed under the Apache License, Version2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * SPDX-License-Identifier: Apache-2.0 19 | */ 20 | 21 | public class Main 22 | { 23 | public static void main(String[] args) 24 | { 25 | SecretsProcessor secretsProcessor = new SecretsProcessor(); 26 | secretsProcessor.processSecret(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /monitors/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | %d{yyyy-MM-dd'T'HH:mm:ss.SSSZ} %X{CID:--} %-5p [%t] %c: %m%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #============================================================================================== 4 | # Build the full class-path of all the JARs found 5 | CP="" 6 | for I in /app/agent/*.jar; do 7 | CP="$CP:$I" 8 | done 9 | 10 | #============================================================================================== 11 | # Source environment variables for environment properties 12 | . /app/config/env.properties 13 | 14 | # set defaults for variables not defined 15 | [ -z "$LOG_LEVEL" ] && LOG_LEVEL="WARN" 16 | [ -z "$LOG_CONFIG" ] && LOG_CONFIG="/app/monitors/log4j2.xml" 17 | 18 | #============================================================================================== 19 | # System properties 20 | # Logback configuration file and default level 21 | SYS_OPTS="-Dlog4j.configurationFile=file:$LOG_CONFIG -DLogLevel=$LOG_LEVEL" 22 | 23 | # Check on available log directories 24 | if [ -d "/app/log" ] ; then # Kubernetes style 25 | LOGDIR="/app/log" 26 | else 27 | LOGDIR="/app/dumps" # "default" for local deployments 28 | fi 29 | SYS_OPTS="$SYS_OPTS -Dlog.dir=$LOGDIR" 30 | #echo "SYS_OPTS = $SYS_OPTS" 31 | 32 | #============================================================================================== 33 | # Run 34 | echo "java ${SYS_OPTS} -cp ${CP} com.secretsagent.Main $@" 35 | java ${SYS_OPTS} -cp ${CP} com.secretsagent.Main $@ -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # Run a "Local" docker image on the workstation 5 | # 6 | # 1) builds the image 7 | # 2) starts the server and shuts down as soon as job completes 8 | # 3) cleanly shuts down on ^C 9 | 10 | SERVICE_NAME="AWS Secrets Agent" 11 | SERVICE_LOG="AwsSecretsAgent.log" 12 | 13 | # Container and Image name 14 | NAME="awssec" 15 | IMAGE="${NAME}:latest" 16 | 17 | ########################################################################## 18 | # Changes terminal window name/tab 19 | set_title() { 20 | # tab 21 | echo -ne "\033]30;$SERVICE_NAME ($1)\007" 22 | # window 23 | echo -ne "\033]0;$SERVICE_NAME ($1)\007" 24 | } 25 | 26 | ########################################################################## 27 | # Kills everything we started 28 | kill_it() { 29 | # attempt to gracefully exit 30 | docker exec ${NAME} /app/bin/stop.sh 31 | # echo -e "\nSleeping for 10 seconds for graceful shutdown...\n" 32 | # sleep 10 33 | docker rm -f ${NAME} 34 | set_title "Stopped" 35 | } 36 | 37 | ########################################################################## 38 | # Script starts here 39 | ########################################################################## 40 | DIR=$(dirname $0) 41 | [ "$DIR" = "." ] && DIR=$(pwd) 42 | 43 | # this is used to get aws credentials when running in a local docker container 44 | AWS_DIR=$(cd $HOME/.aws ; pwd) 45 | 46 | # stop any running containers with this name 47 | docker rm -f ${NAME} 48 | 49 | # build the image locally 50 | #mvn -U clean install || exit 1 51 | 52 | # set up the logs directory in a way that it can be easily deleted 53 | mkdir -p ${DIR}/target/logs 54 | 55 | # trap INT so we can kill the background server task 56 | trap 'kill_it' 2 57 | 58 | echo 59 | echo "=========================================================================" 60 | echo "Starting Docker image $NAME" 61 | echo "=========================================================================" 62 | set_title "Running" 63 | 64 | # this runs and exits as soon as the job is complete 65 | docker run --rm --name=${NAME} \ 66 | --user=$(id -u):$(id -g) \ 67 | -v ${DIR}/local/config:/app/config \ 68 | -v ${DIR}/local/secrets:/app/secrets \ 69 | -v ${DIR}/target/logs:/app/dumps \ 70 | -v ${AWS_DIR}:/.aws \ 71 | ${IMAGE} 72 | -------------------------------------------------------------------------------- /src/test/java/com/secretsagent/SecretsManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.secretsagent; 2 | 3 | import com.amazonaws.services.secretsmanager.AWSSecretsManager; 4 | import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest; 5 | import com.amazonaws.services.secretsmanager.model.GetSecretValueResult; 6 | import org.mockito.Mock; 7 | import org.mockito.Mockito; 8 | import org.mockito.MockitoAnnotations; 9 | import org.testng.Assert; 10 | import org.testng.annotations.AfterMethod; 11 | import org.testng.annotations.BeforeMethod; 12 | import org.testng.annotations.Test; 13 | 14 | import static org.mockito.ArgumentMatchers.any; 15 | import static org.mockito.Mockito.when; 16 | 17 | /** 18 | * Copyright 2020 Comcast Cable Communications Management, LLC 19 | * 20 | * Licensed under the Apache License, Version2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | * 32 | * SPDX-License-Identifier: Apache-2.0 33 | */ 34 | 35 | public class SecretsManagerTest 36 | { 37 | @Mock 38 | AWSSecretsManager awsSecretsManagerClient; 39 | 40 | @BeforeMethod 41 | public void setUp() 42 | { 43 | MockitoAnnotations.initMocks(this); 44 | } 45 | 46 | @AfterMethod 47 | public void tearDown() 48 | { 49 | // report Mockito problems in the test where they occur (default is in the next run) 50 | Mockito.validateMockitoUsage(); 51 | 52 | // this may be needed to reset the mock 53 | Mockito.reset(awsSecretsManagerClient); 54 | } 55 | 56 | @Test 57 | public void testGetSecret_success() 58 | { 59 | String testSecretName = "my-secret"; 60 | String expectedSecret = "my-secret-value"; 61 | 62 | GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest() 63 | .withSecretId(testSecretName); 64 | 65 | GetSecretValueResult getSecretValueResult = new GetSecretValueResult() 66 | .withSecretString(expectedSecret); 67 | 68 | when(awsSecretsManagerClient.getSecretValue(getSecretValueRequest)).thenReturn(getSecretValueResult); 69 | 70 | // test 71 | SecretsManager secretsManager = new SecretsManager(awsSecretsManagerClient); 72 | String actualSecret = secretsManager.getSecret(testSecretName); 73 | 74 | // verify 75 | Assert.assertEquals(actualSecret, expectedSecret); 76 | 77 | // verify getSecretValue is called only once and with the expected values 78 | Mockito.verify(awsSecretsManagerClient).getSecretValue(any(GetSecretValueRequest.class)); 79 | Mockito.verify(awsSecretsManagerClient).getSecretValue(getSecretValueRequest); 80 | } 81 | } -------------------------------------------------------------------------------- /src/test/java/com/secretsagent/SecretsProcessorTest.java: -------------------------------------------------------------------------------- 1 | package com.secretsagent; 2 | 3 | import org.mockito.Mock; 4 | import org.mockito.Mockito; 5 | import org.mockito.MockitoAnnotations; 6 | import org.testng.Assert; 7 | import org.testng.annotations.AfterMethod; 8 | import org.testng.annotations.BeforeMethod; 9 | import org.testng.annotations.Test; 10 | 11 | import java.nio.file.Paths; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.Properties; 15 | 16 | import static org.mockito.ArgumentMatchers.anyString; 17 | import static org.mockito.Mockito.when; 18 | 19 | /** 20 | * Copyright 2020 Comcast Cable Communications Management, LLC 21 | * 22 | * Licensed under the Apache License, Version2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and 32 | * limitations under the License. 33 | * 34 | * SPDX-License-Identifier: Apache-2.0 35 | */ 36 | 37 | public class SecretsProcessorTest 38 | { 39 | @Mock 40 | SecretsManager secretsManager; 41 | 42 | Properties properties; 43 | 44 | @BeforeMethod 45 | public void setUp() 46 | { 47 | MockitoAnnotations.initMocks(this); 48 | 49 | properties = new Properties(); 50 | properties.setProperty("secret.name", "test-secret"); 51 | 52 | String directoryPath = Paths.get(System.getProperty("user.dir"), "/local").toString(); 53 | properties.setProperty("directory", directoryPath); 54 | properties.setProperty("secrets.filename", "/secrets/secrets.properties"); 55 | } 56 | 57 | @AfterMethod 58 | public void tearDown() 59 | { 60 | // report Mockito problems in the test where they occur (default is in the next run) 61 | Mockito.validateMockitoUsage(); 62 | 63 | // this may be needed to reset the mock 64 | Mockito.reset(secretsManager); 65 | } 66 | 67 | @Test 68 | public void testProcessSecret() 69 | { 70 | String secretString = "service.status.encryptedPassword=XXXXXXXXXXX==\nauth.accountcache.superUserPasswordEncrypted=XXXXXXXXXXX==\nauth" + 71 | ".accountcache.superUserName=service/user.test@testdomain.com\ndomainregistry.testdomain.auth" + 72 | ".passwordEncrypted=XXXXX==\ndomainregistry.testdomain.auth.userName=service/user.test@testdomain.com"; 73 | 74 | List expectedResult = Arrays.asList(secretString.split("\\\\n")); 75 | 76 | when(secretsManager.getSecret(properties.getProperty("secret.name"))).thenReturn(secretString); 77 | 78 | // test 79 | SecretsProcessor secretsProcessor = new SecretsProcessor(secretsManager, properties); 80 | List actualResult = secretsProcessor.processSecret(); 81 | 82 | // verify 83 | Assert.assertEquals(actualResult, expectedResult); 84 | 85 | // verify getSecretValue is called only once and with the expected values 86 | Mockito.verify(secretsManager).getSecret(anyString()); 87 | Mockito.verify(secretsManager).getSecret(properties.getProperty("secret.name")); 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/java/com/secretsagent/SecretsManager.java: -------------------------------------------------------------------------------- 1 | package com.secretsagent; 2 | 3 | import com.amazonaws.auth.AWSCredentialsProvider; 4 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 5 | import com.amazonaws.auth.profile.ProfileCredentialsProvider; 6 | import com.amazonaws.auth.profile.ProfilesConfigFile; 7 | import com.amazonaws.services.secretsmanager.AWSSecretsManager; 8 | import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder; 9 | import com.amazonaws.services.secretsmanager.model.*; 10 | import org.apache.logging.log4j.Logger; 11 | import org.apache.logging.log4j.LogManager; 12 | 13 | /** 14 | * Copyright 2020 Comcast Cable Communications Management, LLC 15 | * 16 | * Licensed under the Apache License, Version2.0 (the "License"); 17 | * you may not use this file except in compliance with the License. 18 | * You may obtain a copy of the License at 19 | * 20 | * http://www.apache.org/licenses/LICENSE-2.0 21 | * 22 | * Unless required by applicable law or agreed to in writing, software 23 | * distributed under the License is distributed on an "AS IS" BASIS, 24 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | * See the License for the specific language governing permissions and 26 | * limitations under the License. 27 | * 28 | * SPDX-License-Identifier: Apache-2.0 29 | */ 30 | 31 | 32 | public class SecretsManager 33 | { 34 | private static final Logger logger = LogManager.getLogger(SecretsManager.class); 35 | 36 | private AWSSecretsManager awsSecretsManagerClient; 37 | 38 | public SecretsManager(AWSSecretsManager awsSecretsManagerClient) 39 | { 40 | this.awsSecretsManagerClient = awsSecretsManagerClient; 41 | } 42 | 43 | public SecretsManager(boolean isLocalRun, String region) 44 | { 45 | AWSCredentialsProvider credentials = getAwsCredentialsProvider(isLocalRun); 46 | 47 | this.awsSecretsManagerClient = AWSSecretsManagerClientBuilder.standard() 48 | .withCredentials(credentials) 49 | .withRegion(region) 50 | .build(); 51 | } 52 | 53 | public String getSecret(String secretName) 54 | { 55 | GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest() 56 | .withSecretId(secretName); 57 | GetSecretValueResult getSecretValueResult = null; 58 | 59 | try 60 | { 61 | logger.info("Calling AWS SecretsManager"); 62 | getSecretValueResult = awsSecretsManagerClient.getSecretValue(getSecretValueRequest); 63 | } 64 | catch (DecryptionFailureException e) 65 | { 66 | logger.error("Secrets Manager can't decrypt the protected secret text using the provided KMS key."); 67 | throw e; 68 | } 69 | catch (InternalServiceErrorException e) 70 | { 71 | logger.error("An error occurred on the server side."); 72 | throw e; 73 | } 74 | catch (InvalidParameterException e) 75 | { 76 | logger.error("An invalid value was provided for a parameter."); 77 | throw e; 78 | } 79 | catch (InvalidRequestException e) 80 | { 81 | logger.error("A parameter value was provided that is not valid for the current state of the resource."); 82 | throw e; 83 | } 84 | catch (ResourceNotFoundException e) 85 | { 86 | logger.error("Can't find the resource that was requested."); 87 | throw e; 88 | } 89 | 90 | // Returns decrypted secret using the associated KMS CMK. 91 | return getSecretValueResult.getSecretString(); 92 | } 93 | 94 | private AWSCredentialsProvider getAwsCredentialsProvider(Boolean useLocalCredentials) 95 | { 96 | if (useLocalCredentials) 97 | { 98 | return new ProfileCredentialsProvider(new ProfilesConfigFile("../.aws/credentials"), "default"); 99 | } 100 | else 101 | { 102 | return new InstanceProfileCredentialsProvider(false); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/secretsagent/SecretsProcessor.java: -------------------------------------------------------------------------------- 1 | package com.secretsagent; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.logging.log4j.Logger; 5 | import org.apache.logging.log4j.LogManager; 6 | 7 | import java.io.File; 8 | import java.io.FileReader; 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Properties; 17 | 18 | /** 19 | * Copyright 2020 Comcast Cable Communications Management, LLC 20 | * 21 | * Licensed under the Apache License, Version2.0 (the "License"); 22 | * you may not use this file except in compliance with the License. 23 | * You may obtain a copy of the License at 24 | * 25 | * http://www.apache.org/licenses/LICENSE-2.0 26 | * 27 | * Unless required by applicable law or agreed to in writing, software 28 | * distributed under the License is distributed on an "AS IS" BASIS, 29 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | * See the License for the specific language governing permissions and 31 | * limitations under the License. 32 | * 33 | * SPDX-License-Identifier: Apache-2.0 34 | */ 35 | 36 | public class SecretsProcessor 37 | { 38 | public static final String SERVICE_PROPERTIES = "/config/service.properties"; 39 | public static final String CONTAINER_DIRECTORY = "/app"; 40 | public static final String LOCAL_DIRECTORY = "/local"; 41 | 42 | private static final Logger logger = LogManager.getLogger(SecretsProcessor.class); 43 | 44 | SecretsManager secretsManager; 45 | Properties properties; 46 | 47 | public SecretsProcessor(SecretsManager secretsManager, Properties properties) 48 | { 49 | this.secretsManager = secretsManager; 50 | this.properties = properties; 51 | } 52 | 53 | public SecretsProcessor() 54 | { 55 | this.properties = getProperties(); 56 | 57 | String region = properties.getProperty("region"); 58 | boolean isLocalRun = false; 59 | if (StringUtils.isNotEmpty(properties.getProperty("is.local.run"))) 60 | { 61 | isLocalRun = Boolean.parseBoolean(properties.getProperty("is.local.run")); 62 | } 63 | 64 | this.secretsManager = new SecretsManager(isLocalRun, region); 65 | } 66 | 67 | public List processSecret() 68 | { 69 | // *** get secret 70 | String secretName = properties.getProperty("secret.name"); 71 | String secret = secretsManager.getSecret(secretName); 72 | 73 | // *** write file 74 | List lines = Arrays.asList(secret.split("\\\\n")); 75 | 76 | Path secretsPath = Paths.get(properties.getProperty("directory"), properties.getProperty("secrets.filename")); 77 | 78 | try 79 | { 80 | writeFile(lines, secretsPath); 81 | } 82 | catch (IOException ex) 83 | { 84 | logger.error("Error writing to file", ex); 85 | } 86 | 87 | logger.info("Completed successfully."); 88 | 89 | // return lines for testing 90 | return lines; 91 | } 92 | 93 | private Properties getProperties() 94 | { 95 | Properties properties = new Properties(); 96 | 97 | Path directoryPath = Paths.get(CONTAINER_DIRECTORY); 98 | 99 | if (Files.notExists(directoryPath)) 100 | { 101 | directoryPath = Paths.get(System.getProperty("user.dir"), LOCAL_DIRECTORY); 102 | } 103 | String directory = directoryPath.toString(); 104 | 105 | Path propertiesPath = Paths.get(directoryPath.toString(), SERVICE_PROPERTIES); 106 | 107 | try 108 | { 109 | properties.load(new FileReader(String.valueOf(propertiesPath))); 110 | } 111 | catch (IOException e) 112 | { 113 | logger.warn("Unable to load properties."); 114 | } 115 | 116 | properties.setProperty("directory", directory); 117 | 118 | return properties; 119 | } 120 | 121 | private void writeFile(List lines, Path path) throws IOException 122 | { 123 | logger.info("Attempting to write to: {}", path); 124 | 125 | // create the parent directories if they don't exist 126 | File file = new File(path.toString()); 127 | file.getParentFile().mkdirs(); 128 | 129 | Files.write(path, lines, StandardCharsets.UTF_8); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Secrets Agent 2 | 3 | Reads secrets from AWS SecretsManager and writes to a file. Intended to be run as a kubernetes Init Container to create a secrets.properties file in a volume mount that is 4 | accessible to other containers in the pod. 5 | 6 | ## Configuration Settings 7 | | Key | Required | Description | Example value | 8 | | :--- | :--- | :--- | :--- | 9 | | `secret.name` | Yes | Friendly name of the AWS Secret | `valws/lab.main.t.aor.valws.vaw2` | 10 | | `region` | Yes | AWS Region where secrets are stored | `us-east-1` | 11 | | `secrets.filename` | Yes | The relative path and filename of the output file. | `/secrets/secrets.properties` | 12 | | `is.local.run` | No | Whether this is a local run. Defaults to `false` | `true` | 13 | 14 | If `is.local.run` is `true`: 15 | * /local/config/service.properties is used. 16 | * local AWS credentials are used. 17 | 18 | ## Environment Variables 19 | | Variable | Required | Description | Example value | 20 | | :--- | :--- | :--- | :--- | 21 | | `DOCKER_REPO` | Yes | The Docker repository to deploy the image to | `testrepo` | 22 | 23 | Alternatively, this can be passed in to the maven command using the `-D` option. For example: 24 | `mvn clean install -Denv.DOCKER_REPO=test` 25 | 26 | ## To run and test locally 27 | Run `run.sh` 28 | This runs the Docker container. 29 | 30 | ## Example Kubernetes configurations 31 | 32 | ### Deployment.yaml 33 | * An IAM Role with permissions to access SecretsManager is required. 34 | * AWS Secrets Agent is configured as an Init Container. 35 | * AWS Secrets Agent mounts and writes to a volume that is also mounted by another container in the pod. 36 | * AWS Secrets Agent mounts the config volume and location that matches ConfigMap.yaml. 37 | 38 | In the example below, the following keys are used for AWS Secrets Agent: 39 | * `spec.template.metadata.annotations.iam.amazonaws.com/role` 40 | * `spec.template.spec.initContainers` 41 | * `spec.template.spec.containers..volumeMounts.` 42 | * `spec.template.spec.volumes.` 43 | * `spec.template.spec.volumes.` 44 | 45 | ```yaml 46 | --- 47 | apiVersion: apps/v1 48 | kind: Deployment 49 | metadata: 50 | name: lab-main-t-aor-valws-vaw2 51 | namespace: workflow 52 | labels: 53 | environmentShortName: lab 54 | regionShortName: main 55 | partitionShortName: t 56 | serviceShortName: valws 57 | clusterSuffix: vaw2 58 | clusterTitle: lab-main-t-aor-valws-vaw2 59 | spec: 60 | replicas: 2 61 | revisionHistoryLimit: 2 62 | strategy: 63 | type: RollingUpdate 64 | rollingUpdate: 65 | maxSurge: 25% 66 | maxUnavailable: 25% 67 | selector: 68 | matchLabels: 69 | clusterTitle: lab-main-t-aor-valws-vaw2 70 | template: 71 | metadata: 72 | annotations: 73 | iam.amazonaws.com/role: arn:aws:iam::0000000001:role/CustomerManaged-ValidationWebService 74 | labels: 75 | environmentShortName: lab 76 | regionShortName: main 77 | partitionShortName: t 78 | serviceShortName: valws 79 | clusterSuffix: vaw2 80 | clusterTitle: lab-main-t-aor-valws-vaw2 81 | spec: 82 | initContainers: 83 | - name: awssec 84 | image: docker-lab.repo.com/awssec:0.0.3 85 | volumeMounts: 86 | - name: awssec-config 87 | mountPath: /app/config 88 | - name: app-secrets 89 | mountPath: /app/secrets 90 | containers: 91 | - name: valws 92 | image: docker-lab.repo.com/valws:2.0.8 93 | imagePullPolicy: Always 94 | ports: 95 | - containerPort: 8080 96 | name: service 97 | resources: 98 | requests: 99 | cpu: 100m 100 | memory: 2048Mi 101 | limits: 102 | cpu: 4000m 103 | memory: 2048Mi 104 | readinessProbe: 105 | httpGet: 106 | path: validation/management/alive 107 | port: 8080 108 | initialDelaySeconds: 10 109 | periodSeconds: 10 110 | timeoutSeconds: 60 111 | successThreshold: 1 112 | failureThreshold: 30 113 | livenessProbe: 114 | httpGet: 115 | path: validation/management/alive 116 | port: 8080 117 | initialDelaySeconds: 30 118 | periodSeconds: 20 119 | timeoutSeconds: 60 120 | successThreshold: 1 121 | failureThreshold: 3 122 | volumeMounts: 123 | - name: config 124 | mountPath: /app/config 125 | - name: app-secrets 126 | mountPath: /app/secrets 127 | volumes: 128 | - name: config 129 | configMap: 130 | name: lab-main-t-aor-valws-vaw2 131 | items: 132 | - key: service-properties 133 | path: service.properties 134 | - key: env-properties 135 | path: env.properties 136 | - name: awssec-config 137 | configMap: 138 | name: lab-main-t-aor-valws-vaw2 139 | items: 140 | - key: awssec-properties 141 | path: service.properties 142 | - key: awssec-env-properties 143 | path: env.properties 144 | - name: app-secrets 145 | emptyDir: {} 146 | 147 | ``` 148 | 149 | ### ConfigMap.yaml 150 | In the example below, the following keys are used for AWS Secrets Agent: 151 | * `data.awssec-env-properties` 152 | * `data.awssec-properties` 153 | 154 | ```yaml 155 | --- 156 | apiVersion: v1 157 | kind: ConfigMap 158 | metadata: 159 | name: lab-main-t-aor-valws-vaw2 160 | namespace: workflow 161 | labels: 162 | environmentShortName: lab 163 | regionShortName: main 164 | partitionShortName: t 165 | serviceShortName: valws 166 | clusterSuffix: vaw2 167 | clusterTitle: lab-main-t-aor-valws-vaw2 168 | data: 169 | env-properties: | 170 | ALIVE_MBEAN="application=ValidationService,name=aliveCheckConfiguration" 171 | service-properties: | 172 | ws.base.url=http://validation.test.corp.mycompany.com/validation/web 173 | ws.instance.name=Test 174 | awssec-env-properties: | 175 | LOG_CONFIG=/app/monitors/logback-syslog.xml 176 | LOG_LEVEL=INFO 177 | awssec-properties: | 178 | secret.name=valws/lab.main.t.aor.valws.vaw2 179 | region=us-east-1 180 | secrets.filename=/secrets/secrets.properties 181 | 182 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.secretsagent 9 | aws-secrets-agent 10 | 1.0.0 11 | jar 12 | 13 | AWS Secrets Agent 14 | 15 | 16 | awssec 17 | 1.8 18 | java18 19 | 1.11.816 20 | 2.11.1 21 | 1.14 22 | 3.10 23 | 2.13.3 24 | 3.3.3 25 | 7.1.0 26 | 27 | 28 | 29 | 30 | com.amazonaws 31 | aws-java-sdk-secretsmanager 32 | ${aws.java.sdk.version} 33 | 34 | 35 | com.fasterxml.jackson.core 36 | jackson-core 37 | 38 | 39 | com.fasterxml.jackson.core 40 | jackson-databind 41 | 42 | 43 | commons-codec 44 | commons-codec 45 | 46 | 47 | 48 | 49 | com.fasterxml.jackson.core 50 | jackson-core 51 | ${jackson.version} 52 | 53 | 54 | com.fasterxml.jackson.core 55 | jackson-databind 56 | ${jackson.version} 57 | 58 | 59 | commons-codec 60 | commons-codec 61 | ${commons.codec.version} 62 | 63 | 64 | org.apache.commons 65 | commons-lang3 66 | ${commons.lang3.version} 67 | 68 | 69 | org.apache.logging.log4j 70 | log4j-core 71 | ${log4j.version} 72 | 73 | 74 | 75 | 76 | org.testng 77 | testng 78 | ${testng.version} 79 | test 80 | 81 | 82 | org.mockito 83 | mockito-core 84 | ${mockito.version} 85 | test 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | maven-compiler-plugin 94 | 3.8.1 95 | 96 | 1.8 97 | 1.8 98 | 99 | 100 | 101 | 102 | maven-jar-plugin 103 | 3.2.0 104 | 105 | 106 | 107 | true 108 | lib/ 109 | com.secretsagent.Main 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-dependency-plugin 117 | 118 | 119 | 120 | copy-dependencies 121 | 122 | package 123 | 124 | 125 | 126 | runtime 127 | 128 | 129 | 130 | com.spotify 131 | dockerfile-maven-plugin 132 | 1.4.6 133 | 134 | 135 | 136 | default 137 | 138 | build 139 | push 140 | 141 | 142 | 143 | tag-local-version 144 | 145 | tag 146 | 147 | 148 | ${docker.imageName} 149 | ${project.version} 150 | 151 | 152 | 153 | tag-local-latest 154 | 155 | tag 156 | 157 | 158 | ${docker.imageName} 159 | latest 160 | 161 | 162 | 163 | 164 | ${env.DOCKER_REPO}/${docker.imageName} 165 | ${project.version} 166 | false 167 | 168 | ${project.artifactId}-${project.version}.jar 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------