Skip to content

Commit

Permalink
Feature: AWS client configuration (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskilding authored May 19, 2022
1 parent 8f428ae commit 5510bd7
Show file tree
Hide file tree
Showing 51 changed files with 908 additions and 289 deletions.
26 changes: 15 additions & 11 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ This plugin is the high-level counterpart of the [AWS Secrets Manager SecretSour

## Contents

- [Authentication](authentication/index.md)
- [Beta Features](beta/index.md)
- [Caching](caching/index.md)
- [Client](client/index.md)
- [Filters](filters/index.md)
- [Networking](networking/index.md)
- [Screenshots](screenshots/index.md)
Expand Down Expand Up @@ -64,7 +64,7 @@ Example:

### Jenkins

The plugin uses the AWS Java SDK to communicate with Secrets Manager. If you are running Jenkins outside EC2 or EKS you may need to manually configure the SDK to authenticate with AWS. See the [authentication](authentication/index.md) guide for more information.
The plugin uses the AWS Java SDK to communicate with Secrets Manager. If you are running Jenkins outside EC2 or EKS you may need to manually configure the SDK to authenticate with AWS. See the [client](client/index.md) configuration guide for more information.

Then, install and [configure](#Configuration) the plugin.

Expand Down Expand Up @@ -329,10 +329,11 @@ Go to `Manage Jenkins` > `Configure System` > `AWS Secrets Manager Credentials P

Available settings:

- [Cache](caching/index.md) (on/off)
- Endpoint Configuration
- Service Endpoint
- Signing Region
- [Cache](caching/index.md)
- [Client](client/index.md)
- CredentialsProvider
- Endpoint Configuration
- Region
- ListSecrets configuration
- [Filters](filters/index.md)

Expand All @@ -345,11 +346,14 @@ You can set plugin configuration using Jenkins [Configuration As Code](https://g
```yaml
unclassified:
awsCredentialsProvider:
cache: (boolean) # optional
endpointConfiguration: # optional
serviceEndpoint: (URL)
signingRegion: (string)
listSecrets: # optional
cache: (boolean) # optional
client: # optional
credentialsProvider: (object) # optional
endpointConfiguration: # optional
serviceEndpoint: (URL)
signingRegion: (string)
region: (string) # optional
listSecrets: # optional
filters:
- key: name
values:
Expand Down
20 changes: 0 additions & 20 deletions docs/authentication/index.md

This file was deleted.

81 changes: 81 additions & 0 deletions docs/client/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Client

The plugin allows you to configure the Secrets Manager client that it uses to access secrets.

**We recommend that you use the defaults whenever possible.** This will allow Jenkins to inherit AWS configuration from the environment. Only set these client options if you really need to (for example you have multiple Jenkins AWS plugins installed, and need the Secrets Manager plugin to behave differently to the others).

## Credentials Provider

The plugin supports the following `AWSCredentialsProvider` implementations to authenticate and authorize with Secrets Manager.

*Note: This is not the same thing as a Jenkins `CredentialsProvider`.*

Recommendations:

- Use EC2 Instance Profiles when running Jenkins on EC2.
- Only use the long-lived access key methods when there is no other choice. For example, when Jenkins is running outside of AWS.
- If you see an error along the lines of "Unable to find a region via the region provider chain. Must provide an explicit region in the builder or setup environment to supply a region.", set the region manually.

### Default

This uses the standard AWS credentials lookup chain.

The authentication methods in the chain are:

- EC2 Instance Profiles.
- EC2 Container Service credentials.
- Environment variables (set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` before starting Jenkins).
- Java properties (set `aws.accessKeyId`, `aws.secretKey`, and `aws.region` before starting Jenkins).
- User profile (configure `~/.aws/credentials` before starting Jenkins).
- Web Identity Token credentials.

### Profile

This allows you to use named AWS profiles from `~/.aws/config`.

```yaml
unclassified:
awsCredentialsProvider:
client:
credentialsProvider:
profile:
profileName: "foobar"
```
### STS AssumeRole
This allows you to specify IAM roles inline within Jenkins.
```yaml
unclassified:
awsCredentialsProvider:
client:
credentialsProvider:
assumeRole:
roleArn: "arn:aws:iam::111111111111:role/foo"
roleSessionName: "jenkins"
```
## Endpoint Configuration
You can set the AWS endpoint configuration for the client.
```yaml
unclassified:
awsCredentialsProvider:
client:
endpointConfiguration:
serviceEndpoint: "http://localhost:4584"
signingRegion: "us-east-1"
```
## Region
You can set the AWS region for the client.
```yaml
unclassified:
awsCredentialsProvider:
client:
region: "us-east-1"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.jenkins.plugins.credentials.secretsmanager.config;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.util.ListBoxModel;
import io.jenkins.plugins.credentials.secretsmanager.Messages;
import io.jenkins.plugins.credentials.secretsmanager.config.credentialsProvider.CredentialsProvider;
import io.jenkins.plugins.credentials.secretsmanager.config.credentialsProvider.DefaultAWSCredentialsProviderChain;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.util.Objects;

public class Client extends AbstractDescribableImpl<Client> implements Serializable {

private static final long serialVersionUID = 1L;

private CredentialsProvider credentialsProvider;

private EndpointConfiguration endpointConfiguration;

private String region;

@DataBoundConstructor
public Client(CredentialsProvider credentialsProvider, EndpointConfiguration endpointConfiguration, String region) {
this.credentialsProvider = credentialsProvider;
this.endpointConfiguration = endpointConfiguration;
this.region = region;
}

public EndpointConfiguration getEndpointConfiguration() {
return endpointConfiguration;
}

@DataBoundSetter
public void setEndpointConfiguration(EndpointConfiguration endpointConfiguration) {
this.endpointConfiguration = endpointConfiguration;
}

public CredentialsProvider getCredentialsProvider() {
return credentialsProvider;
}

@DataBoundSetter
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
}

public String getRegion() {
return region;
}

@DataBoundSetter
public void setRegion(String region) {
this.region = Util.fixEmptyAndTrim(region);
}

public AWSSecretsManager build() {
final AWSSecretsManagerClientBuilder builder = AWSSecretsManagerClientBuilder.standard();

if (credentialsProvider != null) {
builder.setCredentials(credentialsProvider.build());
}

if (endpointConfiguration != null) {
builder.setEndpointConfiguration(endpointConfiguration.build());
}

if (region != null && !region.isEmpty()) {
builder.setRegion(region);
}

return builder.build();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Client client = (Client) o;
return Objects.equals(credentialsProvider, client.credentialsProvider) &&
Objects.equals(endpointConfiguration, client.endpointConfiguration) &&
Objects.equals(region, client.region);
}

@Override
public int hashCode() {
return Objects.hash(credentialsProvider, endpointConfiguration, region);
}

@Extension
@Symbol("client")
@SuppressWarnings("unused")
public static class DescriptorImpl extends Descriptor<Client> {

public CredentialsProvider getDefaultCredentialsProvider() {
return new DefaultAWSCredentialsProviderChain();
}

@Override
@Nonnull
public String getDisplayName() {
return Messages.client();
}

public ListBoxModel doFillRegionItems() {
final ListBoxModel regions = new ListBoxModel();
regions.add("", "");
for (Regions s : Regions.values()) {
regions.add(s.getDescription(), s.getName());
}
return regions;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
package io.jenkins.plugins.credentials.secretsmanager.config;

import com.amazonaws.AmazonClientException;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClient;
import com.amazonaws.services.secretsmanager.model.ListSecretsRequest;

import hudson.util.FormValidation;

import com.amazonaws.regions.Regions;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.util.ListBoxModel;
import io.jenkins.plugins.credentials.secretsmanager.Messages;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.POST;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.util.Objects;

import javax.annotation.Nonnull;

import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;

public class EndpointConfiguration extends AbstractDescribableImpl<EndpointConfiguration>
implements Serializable {
private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -95,40 +85,14 @@ public String getDisplayName() {
return Messages.endpointConfiguration();
}

/**
* Test the endpoint configuration.
*
* @param serviceEndpoint the AWS service endpoint e.g. http://localhost:4584
* @param signingRegion the AWS signing region e.g. us-east-1
* @return a success or failure indicator
*/
@POST
@SuppressWarnings("unused")
public FormValidation doTestEndpointConfiguration(
@QueryParameter("serviceEndpoint") final String serviceEndpoint,
@QueryParameter("signingRegion") final String signingRegion) {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);

final AwsClientBuilder.EndpointConfiguration ec =
new AwsClientBuilder.EndpointConfiguration(serviceEndpoint, signingRegion);
final AWSSecretsManager client =
AWSSecretsManagerClient.builder().withEndpointConfiguration(ec).build();

final int statusCode;
try {
statusCode = client.listSecrets(new ListSecretsRequest())
.getSdkHttpMetadata()
.getHttpStatusCode();
} catch (AmazonClientException ex) {
final String msg = Messages.awsClientError() + ": '" + ex.getMessage() + "'";
return FormValidation.error(msg);
}

if ((statusCode >= 200) && (statusCode <= 399)) {
return FormValidation.ok(Messages.success());
} else {
return FormValidation.error(Messages.awsServerError() + ": HTTP " + statusCode);
public ListBoxModel doFillSigningRegionItems() {
final ListBoxModel regions = new ListBoxModel();
regions.add("", "");
for (Regions s : Regions.values()) {
regions.add(s.getDescription(), s.getName());
}
return regions;
}

}
}
Loading

0 comments on commit 5510bd7

Please sign in to comment.