Skip to content

Upgrade to AWS SDK v2 with IAM Instance profile & Assume Role support#477

Merged
chadlwilson merged 16 commits into
gocd:masterfrom
arvindsv:upgrade-to-aws-sdk-v2
Jul 20, 2026
Merged

Upgrade to AWS SDK v2 with IAM Instance profile & Assume Role support#477
chadlwilson merged 16 commits into
gocd:masterfrom
arvindsv:upgrade-to-aws-sdk-v2

Conversation

@arvindsv

Copy link
Copy Markdown
Member

Upgrading to AWS SDK v2, since the older SDK v1 is deprecated and out of support. As it stands, the tests pass fine but there are two big issues with this pull request that I will mention in the comments.

arvindsv added 6 commits June 6, 2026 22:57
See: https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/migration-howto.html

Used this init.gradle file:

==============
initscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2" }
    }
    dependencies {
        classpath("org.openrewrite:plugin:7.15.0")
    }
}

rootProject {
    plugins.apply(org.openrewrite.gradle.RewritePlugin)
    dependencies {
        rewrite("software.amazon.awssdk:v2-migration:latest.release")
    }

    afterEvaluate {
        if (repositories.isEmpty()) {
            repositories {
                mavenCentral()
            }
        }
    }
}
==============

Ran this:
./gradlew rewriteRun --init-script init.gradle \
  -Drewrite.activeRecipes=software.amazon.awssdk.v2migration.AwsSdkJavaV1ToV2
This version supports AWS SDK v2.x
- SDKGlobalConfiguration has been removed. No replacement seems to be in
  place. So, the names of the environment variables (which have changed
  a bit, as mentioned in
  https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/migration-env-and-system-props.html)
  have been added to a new class called AwsSdkNames.

 - Remove unnecessary mocking in AWSClientFactoryTest, which had hidden
   the fact that region was not present in the setup.
The default does seem to be "sync" rather than async, but the code that
was automatically migrated in commit
da6c6b2 explicitly set it to use sync.
@arvindsv

This comment was marked as resolved.

@arvindsv

arvindsv commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

The second issue is the tiny matter that it ... doesn't work yet.

Secret config
<secretConfig id="test" pluginId="com.thoughtworks.gocd.secretmanager.aws">
  <configuration>
    <property>
      <key>Endpoint</key>
      <value>https://secretsmanager.us-east-1.amazonaws.com</value>
    </property>
    <property>
      <key>AccessKey</key>
      <encryptedValue>AES:SET-THIS-UP</encryptedValue>
    </property>
    <property>
      <key>SecretAccessKey</key>
      <encryptedValue>AES:SET-THIS-UP</encryptedValue>
    </property>
    <property>
      <key>Region</key>
      <value>us-east-1</value>
    </property>
    <property>
      <key>SecretName</key>
      <value>path/to/my/secret</value>
    </property>
    <property>
      <key>SecretCacheTTL</key>
      <value />
    </property>
  </configuration>
  <rules>
    <allow action="refer" type="pipeline_group">testgroup</allow>
  </rules>
  <description>Test secrets manager</description>
</secretConfig>
Pipeline config
<pipelines group="testgroup">
  <pipeline name="test1">
    <materials>
      <git url="https://github.com/arvindsv/faketime" />
    </materials>
    <stage name="stage1">
      <jobs>
        <job name="job1">
          <environmentvariables>
            <variable name="MY_SECRET">
              <value>{{SECRET:[test][test1]}}</value>
            </variable>
          </environmentvariables>
          <tasks>
            <exec command="/bin/bash">
              <arg>-c</arg>
              <arg>echo "${MY_SECRET}" | rev</arg>
              <runif status="passed" />
            </exec>
          </tasks>
        </job>
      </jobs>
    </stage>
  </pipeline>
</pipelines>
AWS secrets manager setup
$ aws --region us-east-1 secretsmanager create-secret --name 'path/to/my/secret' --description 'Test secret' --secret-string '{"test1": "secret1"}'
{
    "ARN": "arn:aws:secretsmanager:us-east-1:699591462155:secret:path/to/my/secret-Uly5ll",
    "Name": "path/to/my/secret",
    "VersionId": "89ecff4d-4415-4c45-a241-e389c52e8480"
}

It fails with this stack trace:

jvm 1    | 2026-06-13 22:49:24,515 ERROR [130@MessageListener for WorkFinder] JMSMessageListenerAdapter:87 - Exception thrown in message handling by listener com.thoughtworks.go.server.messaging.scheduling.WorkFinder@924d228
jvm 1    | com.thoughtworks.go.plugin.access.exceptions.SecretResolutionFailureException: Expected plugin to resolve secret param(s) `test1` using secret config `test` but plugin failed to resolve any of the required secrets `test1` due to a plugin issue `java.lang.RuntimeException: jdk/net/Sockets`. Please check the plugin configuration or dependencies.
jvm 1    | 	at com.thoughtworks.go.plugin.access.exceptions.SecretResolutionFailureException.withBrokenResolution(SecretResolutionFailureException.java:40)
jvm 1    | 	at com.thoughtworks.go.plugin.access.secrets.SecretsExtension.lookupSecrets(SecretsExtension.java:87)
jvm 1    | 	at com.thoughtworks.go.server.service.SecretParamResolver.lambda$lookupAndUpdateSecretParamsValue$0(SecretParamResolver.java:189)
jvm 1    | 	at java.base/java.util.HashMap.forEach(HashMap.java:1430)
jvm 1    | 	at com.thoughtworks.go.server.service.SecretParamResolver.resolve(SecretParamResolver.java:178)
jvm 1    | 	at com.thoughtworks.go.server.service.SecretParamResolver.resolve(SecretParamResolver.java:117)
jvm 1    | 	at com.thoughtworks.go.server.service.BuildAssignmentService.lambda$createWork$1(BuildAssignmentService.java:321)
jvm 1    | 	at com.thoughtworks.go.server.transaction.TransactionTemplate.lambda$execute$0(TransactionTemplate.java:31)
jvm 1    | 	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
jvm 1    | 	at com.thoughtworks.go.server.transaction.TransactionTemplate.execute(TransactionTemplate.java:28)
jvm 1    | 	at com.thoughtworks.go.server.service.BuildAssignmentService.lambda$createWork$0(BuildAssignmentService.java:302)
jvm 1    | 	at com.thoughtworks.go.server.transaction.TransactionTemplate.transactionSurrounding(TransactionTemplate.java:60)
jvm 1    | 	at com.thoughtworks.go.server.service.BuildAssignmentService.createWork(BuildAssignmentService.java:286)
jvm 1    | 	at com.thoughtworks.go.server.service.BuildAssignmentService.assignWorkToAgent(BuildAssignmentService.java:184)
jvm 1    | 	at com.thoughtworks.go.server.service.BuildAssignmentService.assignWorkToAgent(BuildAssignmentService.java:163)
jvm 1    | 	at com.thoughtworks.go.server.messaging.scheduling.WorkFinder.onMessage(WorkFinder.java:61)
jvm 1    | 	at com.thoughtworks.go.server.messaging.scheduling.WorkFinder.onMessage(WorkFinder.java:32)
jvm 1    | 	at com.thoughtworks.go.server.messaging.activemq.JMSMessageListenerAdapter.runImpl(JMSMessageListenerAdapter.java:83)
jvm 1    | 	at com.thoughtworks.go.server.messaging.activemq.JMSMessageListenerAdapter.run(JMSMessageListenerAdapter.java:62)
jvm 1    | 	at java.base/java.lang.Thread.run(Thread.java:1474)
jvm 1    | Caused by: java.lang.RuntimeException: jdk/net/Sockets
jvm 1    | 	at com.thoughtworks.go.plugin.infra.FelixGoPluginOSGiFramework.executeActionOnTheService(FelixGoPluginOSGiFramework.java:207)
jvm 1    | 	at com.thoughtworks.go.plugin.infra.FelixGoPluginOSGiFramework.doOn(FelixGoPluginOSGiFramework.java:161)
jvm 1    | 	at com.thoughtworks.go.plugin.infra.DefaultPluginManager.submitTo(DefaultPluginManager.java:141)
jvm 1    | 	at com.thoughtworks.go.plugin.access.PluginRequestHelper.submitRequest(PluginRequestHelper.java:49)
jvm 1    | 	at com.thoughtworks.go.plugin.access.secrets.v1.SecretsExtensionV1.lookupSecrets(SecretsExtensionV1.java:99)
jvm 1    | 	at com.thoughtworks.go.plugin.access.secrets.SecretsExtension.lookupSecrets(SecretsExtension.java:83)
jvm 1    | 	... 18 common frames omitted
jvm 1    | Caused by: java.lang.NoClassDefFoundError: jdk/net/Sockets
jvm 1    | 	at org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator.<clinit>(DefaultHttpClientConnectionOperator.java:87)
jvm 1    | 	at software.amazon.awssdk.http.apache5.internal.conn.SafePoolingHttpClientConnectionManagerBuilder.createConnectionOperator(SafePoolingHttpClientConnectionManagerBuilder.java:207)
jvm 1    | 	at software.amazon.awssdk.http.apache5.internal.conn.SafePoolingHttpClientConnectionManagerBuilder.build(SafePoolingHttpClientConnectionManagerBuilder.java:222)
jvm 1    | 	at software.amazon.awssdk.http.apache5.Apache5HttpClient$ApacheConnectionManagerFactory.create(Apache5HttpClient.java:814)
jvm 1    | 	at software.amazon.awssdk.http.apache5.Apache5HttpClient.createClient(Apache5HttpClient.java:180)
jvm 1    | 	at software.amazon.awssdk.http.apache5.Apache5HttpClient.<init>(Apache5HttpClient.java:153)
jvm 1    | 	at software.amazon.awssdk.http.apache5.Apache5HttpClient.<init>(Apache5HttpClient.java:132)
jvm 1    | 	at software.amazon.awssdk.http.apache5.Apache5HttpClient$DefaultBuilder.buildWithDefaults(Apache5HttpClient.java:757)
jvm 1    | 	at software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder.lambda$buildWithDefaults$0(DefaultSdkHttpClientBuilder.java:44)
jvm 1    | 	at java.base/java.util.Optional.map(Optional.java:260)
jvm 1    | 	at software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder.buildWithDefaults(DefaultSdkHttpClientBuilder.java:44)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.lambda$resolveSyncHttpClient$18(SdkDefaultClientBuilder.java:480)
jvm 1    | 	at java.base/java.util.Optional.orElseGet(Optional.java:364)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.resolveSyncHttpClient(SdkDefaultClientBuilder.java:480)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.lambda$finalizeSyncConfiguration$5(SdkDefaultClientBuilder.java:316)
jvm 1    | 	at software.amazon.awssdk.utils.AttributeMap$DerivedValue.primeCache(AttributeMap.java:604)
jvm 1    | 	at software.amazon.awssdk.utils.AttributeMap$DerivedValue.get(AttributeMap.java:593)
jvm 1    | 	at software.amazon.awssdk.utils.AttributeMap$Builder.resolveValue(AttributeMap.java:400)
jvm 1    | 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
jvm 1    | 	at software.amazon.awssdk.utils.AttributeMap$Builder.build(AttributeMap.java:362)
jvm 1    | 	at software.amazon.awssdk.core.client.config.SdkClientConfiguration$Builder.build(SdkClientConfiguration.java:224)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.finalizeSyncConfiguration(SdkDefaultClientBuilder.java:318)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.syncClientConfiguration(SdkDefaultClientBuilder.java:207)
jvm 1    | 	at software.amazon.awssdk.services.secretsmanager.DefaultSecretsManagerClientBuilder.buildClient(DefaultSecretsManagerClientBuilder.java:38)
jvm 1    | 	at software.amazon.awssdk.services.secretsmanager.DefaultSecretsManagerClientBuilder.buildClient(DefaultSecretsManagerClientBuilder.java:25)
jvm 1    | 	at software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder.build(SdkDefaultClientBuilder.java:173)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.SecretManagerClient.getAwsSecretsManager(SecretManagerClient.java:62)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.SecretManagerClient.<init>(SecretManagerClient.java:39)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.AWSClientFactory.client(AWSClientFactory.java:49)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.SecretConfigLookupExecutor.execute(SecretConfigLookupExecutor.java:51)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.SecretConfigLookupExecutor.execute(SecretConfigLookupExecutor.java:34)
jvm 1    | 	at cd.go.plugin.base.executors.secrets.LookupExecutor.execute(LookupExecutor.java:28)
jvm 1    | 	at cd.go.plugin.base.dispatcher.RequestDispatcher.dispatch(RequestDispatcher.java:39)
jvm 1    | 	at com.thoughtworks.gocd.secretmanager.aws.AwsPlugin.handle(AwsPlugin.java:54)
jvm 1    | 	at com.thoughtworks.go.plugin.infra.DefaultPluginManager.lambda$submitTo$0(DefaultPluginManager.java:144)
jvm 1    | 	at com.thoughtworks.go.plugin.infra.FelixGoPluginOSGiFramework.executeActionOnTheService(FelixGoPluginOSGiFramework.java:202)
jvm 1    | 	... 23 common frames omitted
jvm 1    | Caused by: java.lang.ClassNotFoundException: jdk.net.Sockets not found by com.thoughtworks.gocd.secretmanager.aws [6]
jvm 1    | 	at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1591)
jvm 1    | 	at org.apache.felix.framework.BundleWiringImpl.access$300(BundleWiringImpl.java:79)
jvm 1    | 	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1976)
jvm 1    | 	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
jvm 1    | 	... 59 common frames omitted

I'm using this Java version:

$ java -version
openjdk version "25.0.2" 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-69)
OpenJDK 64-Bit Server VM (build 25.0.2+10-69, mixed mode, sharing)

I haven't dug depeer into the issue yet. I'm thinking it's one of two problems:

  1. With all the recent / not-so-recent module-related changes in Java / JRE, this jdk.net module isn't a part of the JRE I'm using.
  2. Or, the pass-through packages to plugins need to be changed to include jdk.net.*.

I suspect it's the first one. The reason we wanted to pass through some packages is so that Felix / OSGi will not have those classes separately in the plugin classloader, which would make it hard to send messages back and forth. Even without that, the classes would have been available within the plugin. So, it's unlikely to be the second possibility mentioned above.

@arvindsv

arvindsv commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

I wrote a throw-away test which connects to the real AWS secrets manager and it works fine. So, I'm leaning more towards the OSGi related possibility. Needs more investigation.

@Test
void shouldDoItForReal() {
    SecretConfigLookupExecutor executor = new SecretConfigLookupExecutor(new AWSClientFactory(new AWSCredentialsProviderChain()));

    GoPluginApiResponse response = executor.execute(new SecretConfigRequest() {
        @Override
        public SecretConfig getConfiguration() {
            return new SecretConfig("https://secretsmanager.us-east-1.amazonaws.com", "MY_KEY", "MY_SECRET", "us-east-1") {
                @Override
                public String getSecretName() {
                    return "path/to/my/secret/1";
                }
            };
        }

        @Override
        public List<String> getKeys() {
            return Arrays.asList("test1");
        }
    });

    assertThat(response.responseCode()).isEqualTo(200);
    assertThat(response.responseBody()).isEqualTo("[{\"key\":\"test1\",\"value\":\"secret1\"}]");
}

@chadlwilson

chadlwilson commented Jun 14, 2026

Copy link
Copy Markdown
Member

I recall now there is this config here which I understand restricts the classloading outside the plugin itself to the bootclassloader:

https://github.com/gocd/gocd/blob/7d49110afcae82c35bb2829a394d15b5c2acd58d/plugin-infra/go-plugin-infra/src/main/java/com/thoughtworks/go/plugin/infra/FelixGoPluginOSGiFramework.java#L130

IIRC Apache HttpClient folks started using jdk.net in a 5.x patch release for spurious reasons, IMHO a bad idea - yet they persist with being obstinate https://lists.apache.org/thread/5pmyy8xclqwht49gccy3xvnd2pmv2pz4

I think it will likely not work on any current GoCD container images because we use Temurin JREs and I don't think they include this module? I think java --list-modules will show what's there on your current one anyway. The Linux packages also declare preferences for a "runtime" which may or may not (probably not) have these modules present.

If it's not solely an OSGI thing, unless they have reverted this in a subsequent release we can update to, perhaps should consider using a different client. https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration.html

Not that I like the options very much.

Netty is also pretty big/complex and async first, httpurlconnection possibly too simple and maybe without pooling (while maybe not needed here, might be needed for the ECS elastic agent plugin). A few other plugins have ended up depending on vertx with netty though so maybe netty isn't so bad. 😬 Otherwise many are on various okio versions including ones needing to embed kotlin. It's really quite a mess 😅

@chadlwilson

Copy link
Copy Markdown
Member

The jar above looks like it is bringing in all possible http client options which is likely why it's so huge, and also doesn't make much sense :)

If we do stick with HttpClient 5 (supposed to be the default?) perhaps we can avoid the jdk.net dependency by providing a HttpClientConnectionOperator to avoid it using https://github.com/apache/httpcomponents-client/blob/rel/v5.6.1/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java (if we have access via the SDK).

@arvindsv

Copy link
Copy Markdown
Member Author

I think it will likely not work on any current GoCD container images because we use Temurin JREs and I don't think they include this module? I think java --list-modules will show what's there on your current one anyway.

It does seem to be there, at least on the gocd/gocd-server:v25.4.0 image. However, I agree with you about thinking about avoiding jdk.net.

$ /gocd-jre/bin/java -version
openjdk version "25.0.1" 2025-10-21 LTS
OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.1+8 (build 25.0.1+8-LTS, mixed mode, sharing)

$ /gocd-jre/bin/java --list-modules
java.base@25.0.1
java.compiler@25.0.1
java.datatransfer@25.0.1
java.desktop@25.0.1
java.instrument@25.0.1
java.logging@25.0.1
java.management@25.0.1
java.management.rmi@25.0.1
java.naming@25.0.1
java.net.http@25.0.1
java.prefs@25.0.1
java.rmi@25.0.1
java.scripting@25.0.1
java.se@25.0.1
java.security.jgss@25.0.1
java.security.sasl@25.0.1
java.smartcardio@25.0.1
java.sql@25.0.1
java.sql.rowset@25.0.1
java.transaction.xa@25.0.1
java.xml@25.0.1
java.xml.crypto@25.0.1
jdk.accessibility@25.0.1
jdk.charsets@25.0.1
jdk.crypto.cryptoki@25.0.1
jdk.dynalink@25.0.1
jdk.graal.compiler@25.0.1
jdk.graal.compiler.management@25.0.1
jdk.httpserver@25.0.1
jdk.incubator.vector@25.0.1
jdk.internal.vm.ci@25.0.1
jdk.jdwp.agent@25.0.1
jdk.jfr@25.0.1
jdk.jsobject@25.0.1
jdk.localedata@25.0.1
jdk.management@25.0.1
jdk.management.agent@25.0.1
jdk.management.jfr@25.0.1
jdk.naming.dns@25.0.1
jdk.naming.rmi@25.0.1
jdk.net@25.0.1
jdk.nio.mapmode@25.0.1
jdk.sctp@25.0.1
jdk.security.auth@25.0.1
jdk.security.jgss@25.0.1
jdk.unsupported@25.0.1
jdk.xml.dom@25.0.1
jdk.zipfs@25.0.1

@arvindsv

arvindsv commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

I recall now there is this config here which I understand restricts the classloading outside the plugin itself to the bootclassloader

Yes, that's the one I mentioned about the "pass-through packages" above. I tried adding jdk.net.* to the end of the javax packages line, but it did not help. I did that after verifying that jdk.net is actually available, but in the system classloader only.

When debugging in the GoCD server process, this works fine:

SecretsExtension.class.getClassLoader().findSystemClass("jdk.net.Sockets")

But this doesn't:

SecretsExtension.class.getClassLoader().findClass("jdk.net.Sockets")

It's possible that the bundle's classloader's ancestor tree doesn't include the classloader which has jdk.net. Unless there is a simple way to setup the bundle / plugin to include jdk.net, it might be easier to try to get rid of it (or Apache HttpClient, as you say) from being a dependency.

I tried adding this to wrapper-properties.conf:

wrapper.java.module.1=jdk.net

I don't remember if that is what brought jdk.net into the classpath or if it was there before. Likely before. That line probably had no effect.

@chadlwilson

chadlwilson commented Jun 15, 2026

Copy link
Copy Markdown
Member

Oh, that's very surprising that it didn't help, unless you were doing interactively and it's some quirk of the debugger context - or it's something to do with the extension being loaded by the parent, but if the class is loaded via something inside the plugin it works differently because of the delegation?

In any case, if jdk.net modules are within "standard" definitions of what is considered a JRE, we should probably change the Felix defaults. They are very stale, predating modularization and all the package moves that entailed - and with 26.1.0 im changing to Java 21 minimum so we can move things forward with limited need to sanity check older runtimes. e.g The org.w3c stuff should be long gone I thought, replaced with com.sun and not to be classloaded directly?

I'll take a look at that bit.

If it helps - i have no concern with bumping plugin major version and changing this plugin (and ECS plugin) to a GoCD 26.1.0 (and Java 21) minimum. IIRC this plugin and the ECS one is one of the less frequently used - I think https://gocd.github.io/gocd-plugin-releases-dashboard/ is relatively reliable an indicator as I've been maintaining it. (Results skewed for plugins used on build.gocd.org to be 30*24 more than they should be, and a bit skewed for some used in ruby-functional-tests)

@chadlwilson

Copy link
Copy Markdown
Member

I tried adding jdk.net.* to the end of the javax packages line, but it did not help. I did that after verifying that jdk.net is actually available, but in the system classloader only.

Ahh, after taking another look, is the reason this didn't work that .* only allows for sub-packages, and the classes are directly in the jdk.net package. So need to add a rule for jdk.net?

If you try with 26.2.0-22834 (before I get to it) this should be fixed via gocd/gocd#14493

I possibly don't fully understand quite what the goals of boot delegation are or how this all works with the GoCD Felix/OSGi plugin setup but I guess it's just to avoid managing manifests, and to ensure certain classes cannot be overridden from within the plugin itself; to give some predictable semantics. In the history, it looks like you were key to the design of this, which is cool! Seems it went through a few quick iterations in 2011/2012 to end up with using boot delegation off the framework classloader which has proven to be very stable. Since then it has roughly stayed the same throughout JDKs and Felix/OSGi versions and don't think the package list has been reviewed since Java 6 era.

Not looking to rock the boat too much anyway - only reason for digging a bit further than needed for this issue was to understand what level of (security?) isolation we are actually getting from plugins with Felix.

Will come back to this in a bit, as I also need to migrate the ECS plugin so I can get rid of its need for use of access keys and use STS and assumed roles properly.

@chadlwilson

Copy link
Copy Markdown
Member

Have resolved the first dependency bloat issue, but pending actual validation and deciding whether we want to stick to HttpClient 5 and thus require GoCD 26.2.0 - or use a different client.

It least gets as far as the below from a quick test with bad creds:

2026-07-12 16:34:44,310 ERROR [WorkFinder-1] SecretConfigLookupExecutor:128 - Failed to lookup secret from AWS.
software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException: Credential should be scoped to a valid region.  (Service: SecretsManager, Status Code: 400, Request ID: a9f51cc0-9d30-4d68-88a9-ad8b449f1489) (SDK Attempt Count: 1)
        at software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException$BuilderImpl.build(SecretsManagerException.java:113)
        at software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException$BuilderImpl.build(SecretsManagerException.java:61)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.utils.RetryableStageHelper.retryPolicyDisallowedRetryException(RetryableStageHelper.java:180)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:86)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:39)
        at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
        at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:53)
        at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:35)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.executeWithTimer(ApiCallTimeoutTrackingStage.java:82)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:62)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:43)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:49)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:32)
        at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
        at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:37)
        at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:26)
        at software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient$RequestExecutionBuilderImpl.execute(AmazonSyncHttpClient.java:214)
        at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.invoke(BaseSyncClientHandler.java:103)
        at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.doExecute(BaseSyncClientHandler.java:173)
        at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.lambda$execute$1(BaseSyncClientHandler.java:80)
        at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.measureApiCallSuccess(BaseSyncClientHandler.java:182)
        at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:74)
        at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:45)
        at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:53)
        at software.amazon.awssdk.services.secretsmanager.DefaultSecretsManagerClient.describeSecret(DefaultSecretsManagerClient.java:1120)
        at com.amazonaws.secretsmanager.caching.cache.SecretCacheItem.executeRefresh(SecretCacheItem.java:103)
        at com.amazonaws.secretsmanager.caching.cache.SecretCacheItem.executeRefresh(SecretCacheItem.java:33)
        at com.amazonaws.secretsmanager.caching.cache.SecretCacheObject.refresh(SecretCacheObject.java:176)
        at com.amazonaws.secretsmanager.caching.cache.SecretCacheObject.getSecretValue(SecretCacheObject.java:244)
        at com.amazonaws.secretsmanager.caching.SecretCache.getSecretString(SecretCache.java:161)
        at com.thoughtworks.gocd.secretmanager.aws.SecretManagerClient.lookup(SecretManagerClient.java:48)
        at com.thoughtworks.gocd.secretmanager.aws.SecretConfigLookupExecutor.execute(SecretConfigLookupExecutor.java:59)
        at com.thoughtworks.gocd.secretmanager.aws.SecretConfigLookupExecutor.execute(SecretConfigLookupExecutor.java:34)
        at cd.go.plugin.base.executors.secrets.LookupExecutor.execute(LookupExecutor.java:28)
        at cd.go.plugin.base.dispatcher.RequestDispatcher.dispatch(RequestDispatcher.java:39)
        at com.thoughtworks.gocd.secretmanager.aws.AwsPlugin.handle(AwsPlugin.java:54)
        at com.thoughtworks.go.plugin.infra.DefaultPluginManager.lambda$submitTo$0(DefaultPluginManager.java:144)
        at com.thoughtworks.go.plugin.infra.FelixGoPluginOSGiFramework.executeActionOnTheService(FelixGoPluginOSGiFramework.java:227)
        at com.thoughtworks.go.plugin.infra.FelixGoPluginOSGiFramework.doOn(FelixGoPluginOSGiFramework.java:180)
        at com.thoughtworks.go.plugin.infra.DefaultPluginManager.submitTo(DefaultPluginManager.java:141)
        at com.thoughtworks.go.plugin.access.PluginRequestHelper.submitRequest(PluginRequestHelper.java:49)
        at com.thoughtworks.go.plugin.access.secrets.v1.SecretsExtensionV1.lookupSecrets(SecretsExtensionV1.java:99)
        at com.thoughtworks.go.plugin.access.secrets.SecretsExtension.lookupSecrets(SecretsExtension.java:83)
        at com.thoughtworks.go.server.service.SecretParamResolver.lambda$lookupAndUpdateSecretParamsValue$0(SecretParamResolver.java:198)
        at java.base/java.util.HashMap.forEach(HashMap.java:1430)
        at com.thoughtworks.go.server.service.SecretParamResolver.resolve(SecretParamResolver.java:187)
        at com.thoughtworks.go.server.service.SecretParamResolver.resolve(SecretParamResolver.java:126)
        at com.thoughtworks.go.server.service.BuildAssignmentService.lambda$createWork$1(BuildAssignmentService.java:324)
        at com.thoughtworks.go.server.transaction.TransactionTemplate.lambda$execute$0(TransactionTemplate.java:31)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
        at com.thoughtworks.go.server.transaction.TransactionTemplate.execute(TransactionTemplate.java:28)
        at com.thoughtworks.go.server.service.BuildAssignmentService.lambda$createWork$0(BuildAssignmentService.java:305)
        at com.thoughtworks.go.server.transaction.TransactionTemplate.transactionSurrounding(TransactionTemplate.java:60)
        at com.thoughtworks.go.server.service.BuildAssignmentService.createWork(BuildAssignmentService.java:289)
        at com.thoughtworks.go.server.service.BuildAssignmentService.assignWorkToAgent(BuildAssignmentService.java:185)
        at com.thoughtworks.go.server.service.BuildAssignmentService.assignWorkToAgent(BuildAssignmentService.java:164)
        at com.thoughtworks.go.server.messaging.scheduling.WorkFinder.onMessage(WorkFinder.java:61)
        at com.thoughtworks.go.server.messaging.scheduling.WorkFinder.onMessage(WorkFinder.java:32)
        at com.thoughtworks.go.server.messaging.activemq.JMSMessageListenerAdapter.runImpl(JMSMessageListenerAdapter.java:90)
        at com.thoughtworks.go.server.messaging.activemq.JMSMessageListenerAdapter.run(JMSMessageListenerAdapter.java:69)
        at java.base/java.lang.Thread.run(Thread.java:1474)

chadlwilson and others added 6 commits July 20, 2026 15:52
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-java](https://github.com/actions/setup-java).


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@df4cb1c...9c091bb)

Updates `actions/setup-java` from 5.2.0 to 5.4.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](actions/setup-java@be666c2...1bcf9fb)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-java
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the gradle-deps group with 2 updates in the / directory: [org.junit:junit-bom](https://github.com/junit-team/junit-framework) and [gradle-wrapper](https://github.com/gradle/gradle).


Updates `org.junit:junit-bom` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](junit-team/junit-framework@r6.1.0...r6.1.1)

Updates `gradle-wrapper` from 9.5.1 to 9.6.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](gradle/gradle@v9.5.1...v9.6.1)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gradle-deps
- dependency-name: gradle-wrapper
  dependency-version: 9.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gradle-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
- not using async interaction mode
- not using post-quantum cryptography
The plugin right now depends on Apache HttpClient 5, which in turn depends on gocd/gocd#14493 which requires
a newer GoCD release. We can drop this commit if we switch to an alternative client.
@chadlwilson
chadlwilson force-pushed the upgrade-to-aws-sdk-v2 branch from 363d05e to 8770d9b Compare July 20, 2026 07:55
… the plugin

Modelled after validated approach from the ECS Elastic Agent plugin; will try and use the IAM profile as an auto-detection source; and try to assume a role if necessary.
@chadlwilson chadlwilson changed the title Upgrade to AWS SDK v2 Upgrade to AWS SDK v2 with IAM Instance profile & Assume Role support Jul 20, 2026
@chadlwilson
chadlwilson marked this pull request as ready for review July 20, 2026 15:06
@chadlwilson
chadlwilson merged commit 4a7f9cd into gocd:master Jul 20, 2026
1 check passed
@chadlwilson

Copy link
Copy Markdown
Member

Let's go with this for now. If someone pipes up wanting to use this on older GoCD versions I'll take a look then about changing the client to work on older versions.

Have also ported the IAM Instance profile and Assume Rule support over here from the ECS plugin and seems to be working OK for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants