Skip to content

Commit

Permalink
Use STS AssumeRole instead of static credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaixiang-AWS committed Feb 3, 2020
1 parent 1d8010a commit acf1cb6
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,36 @@

package com.netflix.spinnaker.igor.codebuild;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider;
import com.amazonaws.services.codebuild.AWSCodeBuildClient;
import com.amazonaws.services.codebuild.AWSCodeBuildClientBuilder;
import com.amazonaws.services.codebuild.model.BatchGetBuildsRequest;
import com.amazonaws.services.codebuild.model.Build;
import com.amazonaws.services.codebuild.model.StartBuildRequest;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;

/** Generates authenticated requests to AWS CodeBuild API for a single configured account */
@RequiredArgsConstructor
public class AwsCodeBuildAccount {
private final AWSCodeBuildClient client;

public AwsCodeBuildAccount(String accessKeyId, String secretAccessKey, String region) {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretAccessKey);
@Autowired AWSSecurityTokenServiceClient stsClient;

public AwsCodeBuildAccount(String accountId, String assumeRole, String region) {
STSAssumeRoleSessionCredentialsProvider credentialsProvider =
new STSAssumeRoleSessionCredentialsProvider.Builder(
getRoleArn(accountId, assumeRole), "spinnaker-session")
.withStsClient(stsClient)
.build();

// TODO: Add client-side rate limiting to avoid getting throttled if necessary
this.client =
(AWSCodeBuildClient)
AWSCodeBuildClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.withCredentials(credentialsProvider)
.withRequestHandlers(new AwsCodeBuildRequestHandler())
.withRegion(region)
.build();
Expand All @@ -48,4 +58,19 @@ public Build startBuild(StartBuildRequest request) {
public Build getBuild(String buildId) {
return client.batchGetBuilds(new BatchGetBuildsRequest().withIds(buildId)).getBuilds().get(0);
}

private String getRoleArn(String accountId, String assumeRole) {
String assumeRoleValue = Objects.requireNonNull(assumeRole, "assumeRole");
if (!assumeRoleValue.startsWith("arn:")) {
/**
* GovCloud and China regions need to have the full arn passed because of differing formats
* Govcloud: arn:aws-us-gov:iam China: arn:aws-cn:iam
*/
assumeRoleValue =
String.format(
"arn:aws:iam::%s:%s",
Objects.requireNonNull(accountId, "accountId"), assumeRoleValue);
}
return assumeRoleValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,37 @@

package com.netflix.spinnaker.igor.codebuild;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.Request;
import com.amazonaws.Response;
import com.amazonaws.handlers.RequestHandler2;
import com.amazonaws.services.codebuild.model.AWSCodeBuildException;
import com.netflix.spinnaker.igor.exceptions.BuildJobError;
import com.netflix.spinnaker.security.AuthenticatedRequest;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AwsCodeBuildRequestHandler extends RequestHandler2 {
@Override
public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) {
final String userAgent =
String.format(
"spinnaker-user/%s spinnaker-executionId/%s",
AuthenticatedRequest.getSpinnakerUser().orElse("unknown"),
AuthenticatedRequest.getSpinnakerExecutionId().orElse("unknown"));

final AmazonWebServiceRequest cloned = request.clone();

cloned.getRequestClientOptions().appendUserAgent(userAgent);
return super.beforeMarshalling(cloned);
}

@Override
public void afterError(Request<?> request, Response<?> response, Exception e) {
if (e instanceof AWSCodeBuildException) {
if (e instanceof AmazonServiceException
&& ((AmazonServiceException) e)
.getErrorType()
.equals(AmazonServiceException.ErrorType.Client)) {
log.warn(e.getMessage());
throw new BuildJobError(e.getMessage());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.netflix.spinnaker.igor.config;

import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.netflix.spinnaker.igor.codebuild.AwsCodeBuildAccount;
import com.netflix.spinnaker.igor.codebuild.AwsCodeBuildAccountRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
Expand All @@ -36,10 +39,17 @@ AwsCodeBuildAccountRepository awsCodeBuildAccountRepository(
.forEach(
a -> {
AwsCodeBuildAccount account =
new AwsCodeBuildAccount(
a.getAccessKeyId(), a.getSecretAccessKey(), a.getRegion());
new AwsCodeBuildAccount(a.getAccountId(), a.getAssumeRole(), a.getRegion());
accounts.addAccount(a.getName(), account);
});
return accounts;
}

@Bean
AWSSecurityTokenServiceClient awsSecurityTokenServiceClient() {
return (AWSSecurityTokenServiceClient)
AWSSecurityTokenServiceClientBuilder.standard()
.withCredentials(DefaultAWSCredentialsProviderChain.getInstance())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class AwsCodeBuildProperties {
public static class Account {
private String name;
private String region;
private String accessKeyId;
private String secretAccessKey;
private String accountId;
private String assumeRole;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.netflix.spinnaker.igor.codebuild

import com.amazonaws.AmazonServiceException
import com.amazonaws.DefaultRequest
import com.amazonaws.Response
import com.amazonaws.services.codebuild.model.InvalidInputException
Expand All @@ -29,9 +30,10 @@ class AwsCodeBuildRequestHandlerSpec extends Specification {
def request = new DefaultRequest(new StartBuildRequest(), "codebuild")
def response = new Response(new StartBuildResult(), null)

def "should throw BuildJobError in case of AWSCodeBuildException"() {
def "should throw BuildJobError in case of a client exception"() {
when:
def exception = new InvalidInputException("err msg")
exception.setErrorType(AmazonServiceException.ErrorType.Client)
handler.afterError(request, response, exception)
then:
BuildJobError err = thrown()
Expand Down

0 comments on commit acf1cb6

Please sign in to comment.