Skip to content

Commit

Permalink
Merge pull request #34 from orkes-io/secrets
Browse files Browse the repository at this point in the history
Added integration support for cloud secret management
  • Loading branch information
gardusig authored Sep 23, 2022
2 parents 2ddf7b5 + 7b29f71 commit 2d0eeb7
Show file tree
Hide file tree
Showing 15 changed files with 501 additions and 57 deletions.
35 changes: 23 additions & 12 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ plugins {

ext {
versions = [
awaitility : '3.1.6',
awaitility : '4.2.0',
commonsLang: '3.12.0',
conductor : '3.8.1',
eureka : '1.10.10',
groovy : '2.5.15',
eureka : '1.10.17',
groovy : '3.0.12',
jackson : '2.11.4!!',
jersey : '1.19.4',
junit : '5.6.3',
junit : '5.9.0',
slf4j : '1.7.36',
spectator : '0.122.0',
spock : '2.1-groovy-2.5',
wiremock : '2.33.2'
spectator : '1.3.7',
spock : '2.2-groovy-2.5',
wiremock : '2.33.2',
awsSsm : '1.12.300',
azureSsm : '4.2.3',
azureIdentity: '1.3.7',
]
}

Expand All @@ -61,15 +64,19 @@ dependencies {
implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'com.squareup.okhttp:logging-interceptor:2.7.5'

implementation 'com.google.code.gson:gson:2.8.1'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'io.gsonfire:gson-fire:1.8.5'

implementation 'io.gsonfire:gson-fire:1.8.3'

implementation 'io.swagger.core.v3:swagger-annotations:2.0.0'
implementation 'io.swagger.core.v3:swagger-annotations:2.2.2'

implementation "org.apache.commons:commons-lang3:${versions.commonsLang}"

implementation 'org.threeten:threetenbp:1.3.5'
implementation 'org.threeten:threetenbp:1.6.1'

//Integrations
implementation "com.amazonaws:aws-java-sdk-ssm:${versions.awsSsm}"
implementation "com.azure:azure-security-keyvault-secrets:${versions.azureSsm}"
implementation "com.azure:azure-identity:${versions.azureIdentity}"

// test dependencies
testImplementation "org.junit.jupiter:junit-jupiter-api:${versions.junit}"
Expand All @@ -81,6 +88,10 @@ dependencies {
testImplementation("com.github.tomakehurst:wiremock-jre8:${versions.wiremock}") {
exclude group: 'com.fasterxml.jackson'
}
testImplementation 'org.mockito:mockito-all:1.10.19'
testImplementation 'org.testcontainers:localstack:1.17.1'
testImplementation 'org.testcontainers:testcontainers:1.17.1'
testImplementation 'com.amazonaws:aws-java-sdk-core:1.12.138'
}

repositories {
Expand Down
89 changes: 49 additions & 40 deletions src/main/java/io/orkes/conductor/client/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public class ApiClient {
private final String basePath;
private final Map<String, String> defaultHeaderMap = new HashMap<String, String>();

private String tempFolderPath = null;
private String tempFolderPath;

private Map<String, Authentication> authentications;

Expand All @@ -79,7 +79,10 @@ public class ApiClient {
private String keySecret;

private String token;
private Object tokenMutex;

private SecretsManager secretsManager;
private String ssmKeyPath;
private String ssmSecretPath;

/*
* Constructor for ApiClient
Expand All @@ -91,20 +94,23 @@ public ApiClient() {

public ApiClient(String basePath) {
this.basePath = basePath;

httpClient = new OkHttpClient();

verifyingSsl = true;

json = new JSON();

// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();
}

this.keyId = null;
this.keySecret = null;
this.token = null;
this.tokenMutex = new Object();
public ApiClient(
String basePath, SecretsManager secretsManager, String keyPath, String secretPath) {
this(basePath);
this.secretsManager = secretsManager;
this.ssmKeyPath = keyPath;
this.ssmSecretPath = secretPath;
try {
this.refreshToken();
} catch (Exception e) {
LOGGER.warn("Failed to set authentication token. Reason: " + e.getMessage());
}
}

public ApiClient(String basePath, String keyId, String keySecret) {
Expand Down Expand Up @@ -382,7 +388,7 @@ public ApiClient addDefaultHeader(String key, String value) {

/**
* The path of temporary folder used to store downloaded files from endpoints with file
* response. The default value is <code>null</code>, i.e. using the system's default tempopary
* response. The default value is <code>null</code>, i.e. using the system's default temporary
* folder.
*
* @see <a href=
Expand Down Expand Up @@ -467,6 +473,14 @@ public ApiClient setWriteTimeout(int writeTimeout) {
return this;
}

public SecretsManager getSecretsManager() {
return secretsManager;
}

public void setSecretsManager(SecretsManager secretsManager) {
this.secretsManager = secretsManager;
}

/**
* Format the given parameter object into string.
*
Expand Down Expand Up @@ -1037,8 +1051,8 @@ public String buildUrl(String path, List<Pair> queryParams, List<Pair> collectio
/**
* Set header parameters to the request builder, including default headers.
*
* @param headerParams Header parameters in the ofrm of Map
* @param reqBuilder Reqeust.Builder
* @param headerParams Header parameters in the from of Map
* @param reqBuilder Request.Builder
*/
public void processHeaderParams(Map<String, String> headerParams, Request.Builder reqBuilder) {
for (Entry<String, String> param : headerParams.entrySet()) {
Expand Down Expand Up @@ -1208,43 +1222,38 @@ private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityExcepti
}
}

public void refreshToken() throws Exception {
public synchronized String getToken() {
return this.token;
}

synchronized void refreshToken() throws Exception {
if (this.getToken() != null) {
return;
}
if (secretsManager != null) {
keyId = secretsManager.getSecret(this.ssmKeyPath);
keySecret = secretsManager.getSecret(this.ssmSecretPath);
}
if (this.keyId == null || this.keySecret == null) {
throw new Exception(
"KeyId and KeySecret must be set in order to get an authentication token");
}
synchronized (this.tokenMutex) {
GenerateTokenRequest generateTokenRequest =
new GenerateTokenRequest().keyId(this.keyId).keySecret(this.keySecret);
Map<String, String> response =
TokenResourceApi.generateTokenWithHttpInfo(this, generateTokenRequest)
.getData();
final String token = response.get("token");
this.setToken(token);
}
}

public String getToken() {
synchronized (this.tokenMutex) {
return this.token;
}
GenerateTokenRequest generateTokenRequest =
new GenerateTokenRequest().keyId(this.keyId).keySecret(this.keySecret);
Map<String, String> response =
TokenResourceApi.generateTokenWithHttpInfo(this, generateTokenRequest).getData();
final String token = response.get("token");
this.setToken(token);
}

void setToken(String token) {
synchronized (this.tokenMutex) {
this.token = token;
}
synchronized void setToken(String token) {
this.token = token;
this.setApiKeyHeader(token);
}

void setApiKeyHeader(String token) {
synchronized (this.tokenMutex) {
ApiKeyAuth apiKeyAuth = new ApiKeyAuth("header", "X-Authorization");
apiKeyAuth.setApiKey(token);
authentications.put("api_key", apiKeyAuth);
}
synchronized void setApiKeyHeader(String token) {
ApiKeyAuth apiKeyAuth = new ApiKeyAuth("header", "X-Authorization");
apiKeyAuth.setApiKey(token);
authentications.put("api_key", apiKeyAuth);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public interface AuthorizationClient {

CreateAccessKeyResponse createAccessKey(String id);

void createAccessKey(String id, SecretsManager secretsManager, String secretPath);

ConductorApplication createApplication(
CreateOrUpdateApplicationRequest createOrUpdateApplicationRequest);

Expand Down
43 changes: 43 additions & 0 deletions src/main/java/io/orkes/conductor/client/SecretsManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2022 Orkes, Inc.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 io.orkes.conductor.client;

import java.util.List;

public interface SecretsManager {

String getSecret(String key);

void storeSecret(String key, String secret);

default String getProperty(String propertyName) {
String property = null;
List<String> tentativeList =
List.of(
propertyName,
propertyName.toUpperCase(),
propertyName.replace('.', '_'),
propertyName.toUpperCase().replace('.', '_'),
propertyName.replace('_', '.'),
propertyName.toUpperCase().replace('_', '.'));
for (String name : tentativeList) {
if (property == null) {
property = System.getenv(name);
}
if (property == null) {
property = System.getProperty(name);
}
}
return property;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.orkes.conductor.client.ApiClient;
import io.orkes.conductor.client.AuthorizationClient;
import io.orkes.conductor.client.SecretsManager;
import io.orkes.conductor.client.http.api.ApplicationResourceApi;
import io.orkes.conductor.client.http.api.AuthorizationResourceApi;
import io.orkes.conductor.client.http.api.GroupResourceApi;
Expand Down Expand Up @@ -134,6 +135,12 @@ public CreateAccessKeyResponse createAccessKey(String id) throws ApiException {
return applicationResourceApi.createAccessKey(id);
}

@Override
public void createAccessKey(String id, SecretsManager secretsManager, String secretPath) {
CreateAccessKeyResponse response = applicationResourceApi.createAccessKey(id);
secretsManager.storeSecret(secretPath, response.getSecret());
}

@Override
public ConductorApplication createApplication(
CreateOrUpdateApplicationRequest createOrUpdateApplicationRequest) throws ApiException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2022 Orkes, Inc.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 io.orkes.conductor.client.secrets.manager.aws;

import io.orkes.conductor.client.SecretsManager;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.retry.PredefinedRetryPolicies;
import com.amazonaws.retry.RetryPolicy;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagement;
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder;
import com.amazonaws.services.simplesystemsmanagement.model.GetParameterRequest;
import com.amazonaws.services.simplesystemsmanagement.model.ParameterType;
import com.amazonaws.services.simplesystemsmanagement.model.PutParameterRequest;

public class AwsSecretsManager implements SecretsManager {

private final AWSSimpleSystemsManagement client;

public AwsSecretsManager(AWSSimpleSystemsManagement awsSimpleSystemsManagement) {
this.client = awsSimpleSystemsManagement;
}

public AwsSecretsManager(AWSCredentialsProvider credentialsProvider, String region) {
this.client = createClient(credentialsProvider, region);
}

public AwsSecretsManager(AWSCredentialsProvider credentialsProvider) {
this.client = createClient(credentialsProvider, getRegion());
}

@Override
public String getSecret(String key) {
GetParameterRequest request =
new GetParameterRequest().withName(key).withWithDecryption(true);
return client.getParameter(request).getParameter().getValue();
}

@Override
public void storeSecret(String key, String secret) {
PutParameterRequest request =
new PutParameterRequest()
.withName(key)
.withType(ParameterType.SecureString)
.withValue(secret)
.withOverwrite(true);
client.putParameter(request);
}

private AWSSimpleSystemsManagement createClient(
AWSCredentialsProvider credentialsProvider, String region) {
RetryPolicy retryPolicy =
new RetryPolicy(
PredefinedRetryPolicies.DEFAULT_RETRY_CONDITION,
PredefinedRetryPolicies.DEFAULT_BACKOFF_STRATEGY,
3,
false);
AWSSimpleSystemsManagementClientBuilder builder =
AWSSimpleSystemsManagementClientBuilder.standard()
.withClientConfiguration(
new ClientConfiguration().withRetryPolicy(retryPolicy))
.withRegion(region)
.withCredentials(credentialsProvider);
return builder.build();
}

private String getRegion() {
return getProperty("aws.region");
}
}
Loading

0 comments on commit 2d0eeb7

Please sign in to comment.