Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #41 from Nike-Inc/feature/sts_auth_java_client
Browse files Browse the repository at this point in the history
Added STS auth support to Java client
  • Loading branch information
melanahammel authored Dec 18, 2018
2 parents e97470a + cb45b72 commit 7d43bcb
Show file tree
Hide file tree
Showing 34 changed files with 666 additions and 2,561 deletions.
131 changes: 14 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,126 +5,23 @@
[![Coverage Status](https://coveralls.io/repos/github/Nike-Inc/cerberus-java-client/badge.svg?branch=master)](https://coveralls.io/github/Nike-Inc/cerberus-java-client)
[![][license img]][license]

A java based client library for Cerberus that's built on top of Nike's Cerberus client.
This is a Java based client library for Cerberus that is built on top of Nike's Cerberus client.

This library acts as a wrapper around the Nike developed Cerberus client by configuring the client to be Cerberus compatible.

To learn more about Cerberus, please see the [Cerberus website](http://engineering.nike.com/cerberus/).

## Quickstart for EC2
## Quickstart for Cerberus Java Client

1. Start with the [quick start guide](http://engineering.nike.com/cerberus/docs/user-guide/quick-start).
2. Add the [Cerberus client dependency](https://bintray.com/nike/maven/cerberus-client) to your build (e.g. Maven, Gradle)
3. Provide an authentication mechanism.
- For local development it is easiest to export a `CERBERUS_TOKEN` that you copied from the Cerberus dashboard.
When running in AWS, your application will not need this environment variable, instead it will automatically
authenticate using its IAM role. Alternatively, set a `cerberus.token` System property.
- If you would like to test IAM authentication locally, you can do that by [assuming a role](http://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html).
4. Access secrets from Cerberus using Java
1. Add the [Cerberus client dependency](https://bintray.com/nike/maven/cerberus-client) to your build (e.g. Maven, Gradle).
1. Access secrets from Cerberus using Java.
``` java
String cerberusUrl = "https://cerberus.example.com";
CerberusClient cerberusClient = DefaultCerberusClientFactory.getClient(cerberusUrl);
String region = "us-west-2";
CerberusClient cerberusClient = DefaultCerberusClientFactory.getClient(cerberusUrl, region);
Map<String,String> secrets = cerberusClient.read("/app/my-sdb-name").getData();
```

## Lambdas

Generally it does NOT make sense to store Lambda secrets in Cerberus for two reasons:

1. Cerberus cannot support the scale that lambdas may need, e.g. thousands of requests per second
1. Lambdas will not want the extra latency needed to authenticate and read from Cerberus

A better solution for Lambda secrets is using the [encrypted environmental variables](http://docs.aws.amazon.com/lambda/latest/dg/env_variables.html)
feature provided by AWS.

Another option is to store Lambda secrets in Cerberus but only read them at Lambda deploy time, then storing them as encrypted
environmental variables, to avoid the extra Cerberus runtime latency.

### Additional permissions

The IAM role assigned to the Lambda function must contain the following policy statement in addition to the above KMS decrypt policy, this is so the Lambda can look up its metadata to automatically authenticate with the Cerberus IAM auth endpoint:

``` json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGetFunctionConfig",
"Effect": "Allow",
"Action": [
"lambda:GetFunctionConfiguration"
],
"Resource": [
"*"
]
}
]
}
```

### Configure the Client

Setup the CERBERUS_ADDR environmental variable and access Cerberus using Java:

``` java
String invokedFunctionArn = context.getInvokedFunctionArn();
CerberusClient cerberusClient = DefaultCerberusClientFactory.getClientForLambda(invokedFunctionArn);
Map<String,String> secrets = cerberusClient.read("/app/my-sdb-name").getData();
```

## More Configuration Options

There are other ways of using this library than the quick start above.

### Configuring the Cerberus URL

Provide the URL directly using the factory method `DefaultCerberusClientFactory.getClient(cerberusUrl)` or use the
`DefaultCerberusUrlResolver` by setting the environment variable `CERBERUS_ADDR` or the JVM system property `cerberus.addr`
and then use the factory method that does not require a URL:

``` java
final CerberusClient cerberusClient = DefaultCerberusClientFactory.getClient();
Map<String,String> secrets = cerberusClient.read("/app/my-sdb-name").getData();
```

### Configuring Credentials

#### Default Credentials Provider Chain

This client uses a provider chain to resolve the token needed to interact with Cerberus.

See `DefaultCerberusCredentialsProviderChain.java` for full usage.

If the client library is running on an EC2 instance, it will attempt to use the instance's assigned IAM role to authenticate
with Cerberus and obtain a token.

If the client library is running in an ECS task, it will attempt to use the task's execution IAM role to authenticate
with Cerberus and obtain a token.

The IAM role must be configured for access to Cerberus before this will work.

The following policy statement must also be assigned to the IAM role, so that the client can automatically decrypt the auth token from the Cerberus IAM auth endpoint:

``` json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowKmsDecrypt",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:*:[Cerberus AWS Account ID]:key/*"
]
}
]
}
```

The account ID in the ARN should be the account ID where Cerberus is deployed. See your company's internal
documentation for the account ID that you should use.
Check out ["Working with AWS Credentials"](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) for more information on how the AWS SDK for Java loads credentials.

## Development

Expand All @@ -134,24 +31,24 @@ First, make sure the following environment variables are set before running the

``` bash
export CERBERUS_ADDR=https://example.cerberus.com
export TEST_ACCOUNT_ID=12345678910
export TEST_ROLE_NAME=integration-test-role
export TEST_REGION=us-west-2
```

Then, make sure AWS credentials have been obtained. One method is by running [gimme-aws-creds](https://github.com/Nike-Inc/gimme-aws-creds):

```bash
gimme-aws-creds
```

Next, in the project directory run:
```gradle
./gradlew integration
```

## Further Details

Cerberus client is a small project. It only has a few classes and they are all fully documented. For further details please see the source code, including javadocs and unit tests.

<a name="license"></a>
## License

Cerberus client is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
Cerberus client is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).

[travis]:https://travis-ci.org/Nike-Inc/cerberus-java-client
[travis img]:https://api.travis-ci.org/Nike-Inc/cerberus-java-client.svg?branch=master
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
version=6.1.0
version=7.0.0
groupId=com.nike
artifactId=cerberus-client
4 changes: 2 additions & 2 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ dependencies {
compileOnly 'com.google.code.findbugs:annotations:3.0.1'

compile "com.amazonaws:aws-java-sdk-core:${AWS_SDK_VERSION}"
compile "com.amazonaws:aws-java-sdk-kms:${AWS_SDK_VERSION}"
compile "com.amazonaws:aws-java-sdk-lambda:${AWS_SDK_VERSION}"
compile "com.amazonaws:aws-java-sdk-sts:${AWS_SDK_VERSION}"

testRuntime 'org.slf4j:slf4j-simple:1.7.25'
testCompile "junit:junit:4.12"
Expand All @@ -64,6 +63,7 @@ dependencies {
testCompile "org.assertj:assertj-core:2.3.0"
testCompile "com.squareup.okhttp3:mockwebserver:3.7.0"
testCompile "commons-io:commons-io:2.4"
testCompile group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0'
}

shadowJar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.nike.cerberus.client.CerberusClient;
import com.nike.cerberus.client.CerberusServerApiException;
import com.nike.cerberus.client.CerberusServerException;
import com.nike.cerberus.client.DefaultCerberusUrlResolver;
import com.nike.cerberus.client.model.CerberusListFilesResponse;
import com.nike.cerberus.client.model.CerberusListResponse;
import com.nike.cerberus.client.model.CerberusResponse;
Expand All @@ -34,36 +33,34 @@
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
* Tests StaticIamRoleCerberusCredentialsProvider class
* Tests StsCerberusCredentialsProvider class
*/
public class CerberusClientTest {

private static final String ROOT_SDB_PATH = "app/cerberus-integration-tests-sdb/";

private static String iam_principal_arn;
private static String region;
private static String cerberusUrl;

private static String secretPath;
private static String sdbFullSecretPath;
private static Map<String, String> secretData;

private static CerberusClient cerberusClient;

private static StaticIamRoleCerberusCredentialsProvider staticIamRoleCerberusCredentialsProvider;
private static StsCerberusCredentialsProvider stsCerberusCredentialsProvider;

@BeforeClass
public static void setUp() {
iam_principal_arn = EnvUtils.getRequiredEnv("TEST_IAM_PRINCIPAL_ARN", "The role to be assume by the integration test");

region = EnvUtils.getRequiredEnv("TEST_REGION");

EnvUtils.getRequiredEnv("CERBERUS_ADDR");
cerberusUrl = EnvUtils.getRequiredEnv("CERBERUS_ADDR");

secretPath = UUID.randomUUID().toString();
sdbFullSecretPath = ROOT_SDB_PATH + secretPath;
Expand All @@ -84,65 +81,14 @@ private Map<String, String> generateNewSecretData() {
}

@Test
public void test_cerberus_client_crud_after_auth_with_account_id_and_role_name() {
Pattern arn_pattern = Pattern.compile("arn:aws:iam::(?<accountId>[0-9].*):role\\/(?<roleName>.*)");
Matcher matcher = arn_pattern.matcher(iam_principal_arn);
if (! matcher.matches()) {
throw new AssertionError("IAM Principal ARN does not match expected format");
}
String account_id = matcher.group("accountId");
String role_name = matcher.group("roleName");

staticIamRoleCerberusCredentialsProvider = new StaticIamRoleCerberusCredentialsProvider(
new DefaultCerberusUrlResolver(),
account_id,
role_name,
region);

cerberusClient = new CerberusClient(new DefaultCerberusUrlResolver(),
staticIamRoleCerberusCredentialsProvider, new OkHttpClient());

// create secret
cerberusClient.write(sdbFullSecretPath, secretData);

// read secret
CerberusResponse cerberusReadResponse = cerberusClient.read(sdbFullSecretPath);
assertEquals(secretData, cerberusReadResponse.getData());

// list secrets
CerberusListResponse cerberusListResponse = cerberusClient.list(ROOT_SDB_PATH);
assertTrue(cerberusListResponse.getKeys().contains(secretPath));

// update secret
Map<String, String> newSecretData = generateNewSecretData();
cerberusClient.write(sdbFullSecretPath, newSecretData);
secretData = newSecretData;

// confirm updated secret data
CerberusResponse cerberusReadResponseUpdated = cerberusClient.read(sdbFullSecretPath);
assertEquals(newSecretData, cerberusReadResponseUpdated.getData());

// delete secret
cerberusClient.delete(sdbFullSecretPath);

// confirm secret is deleted
try {
cerberusClient.read(sdbFullSecretPath);
} catch (CerberusServerException cse) {
assertEquals(404, cse.getCode());
}
}

@Test
public void test_secret_is_deleted_after_auth_with_iam_principal_name() {
public void test_secret_is_deleted_after_auth() {

staticIamRoleCerberusCredentialsProvider = new StaticIamRoleCerberusCredentialsProvider(
new DefaultCerberusUrlResolver(),
iam_principal_arn,
stsCerberusCredentialsProvider = new StsCerberusCredentialsProvider(
cerberusUrl,
region);

cerberusClient = new CerberusClient(new DefaultCerberusUrlResolver(),
staticIamRoleCerberusCredentialsProvider, new OkHttpClient());
cerberusClient = new CerberusClient(cerberusUrl,
stsCerberusCredentialsProvider, new OkHttpClient());

// create secret
cerberusClient.write(sdbFullSecretPath, secretData);
Expand Down Expand Up @@ -178,13 +124,12 @@ public void test_secret_is_deleted_after_auth_with_iam_principal_name() {
@Test
public void test_crud_for_files() {

staticIamRoleCerberusCredentialsProvider = new StaticIamRoleCerberusCredentialsProvider(
new DefaultCerberusUrlResolver(),
iam_principal_arn,
stsCerberusCredentialsProvider = new StsCerberusCredentialsProvider(
cerberusUrl,
region);

cerberusClient = new CerberusClient(new DefaultCerberusUrlResolver(),
staticIamRoleCerberusCredentialsProvider, new OkHttpClient());
cerberusClient = new CerberusClient(cerberusUrl,
stsCerberusCredentialsProvider, new OkHttpClient());

String fileContentStr = "file content string!";
byte[] fileContentArr = fileContentStr.getBytes(StandardCharsets.UTF_8);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018 Nike, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.nike.cerberus.client.auth.aws;

import com.fieldju.commons.EnvUtils;
import com.nike.cerberus.client.auth.CerberusCredentials;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests StsCerberusCredentialsProvider class
*/
public class StsAuthCerberusClientTest {

private String region;
private String cerberusUrl;

@Before
public void setUp() {
region = EnvUtils.getRequiredEnv("TEST_REGION");
cerberusUrl = EnvUtils.getRequiredEnv("CERBERUS_ADDR");
}

@Test
public void test_get_token(){
StsCerberusCredentialsProvider credentialsProvider = new StsCerberusCredentialsProvider(cerberusUrl, region);
CerberusCredentials creds = credentialsProvider.getCredentials();
assertThat(creds).isNotNull();
assertThat(StringUtils.isNotEmpty(creds.getToken()));
}
}
Loading

0 comments on commit 7d43bcb

Please sign in to comment.