davsclaus commented on code in PR #26674:
URL: https://github.com/apache/camel/pull/26674#discussion_r4065198881
##########
components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsClientBuilderUtil.java:
##########
@@ -299,4 +307,16 @@ private static AwsCredentialsProvider
resolveCredentialsProvider(AwsCommonConfig
LOG.trace("No explicit credentials configured, using SDK default
chain");
return null;
}
+
+ private static void
warnOnConflictingCredentialsOptions(AwsCommonConfiguration config) {
Review Comment:
Minor: `profileCredentialsName` is also silently ignored under auto-detect
(`ProfileCredentialsProvider.create()` uses `AWS_PROFILE` / the default
profile), but it isn't part of this check. Worth adding
`ObjectHelper.isNotEmpty(config.getProfileCredentialsName())` so the warning
fires for that case too.
##########
components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsRuntimeCredentialsResolver.java:
##########
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.aws.common;
+
+import java.io.File;
+
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
+import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import
software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import
software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider;
+
+/**
+ * Detects which AWS credentials source applies to the current runtime - JVM
system properties, environment variables,
+ * web identity / IRSA, a shared profile, or ECS / EKS Pod Identity container
credentials - selects the matching
+ * provider, and reports the chosen source at INFO.
+ * <p>
+ * This is an opt-in enhancement over the SDK {@link
DefaultCredentialsProvider} whose purpose is observability. The SDK
+ * chain already resolves credentials in the same order used here, so the
selected source never differs from the SDK;
+ * this class simply makes the resolved source visible in the logs and returns
it as a targeted provider. When no source
+ * is recognised it returns {@code null} so the caller falls back to the SDK
default chain (which also covers EC2
+ * instance metadata).
+ * </p>
+ * <p>
+ * The detected provider is returned as the head of a chain whose tail is the
full {@link DefaultCredentialsProvider},
+ * so a detected-but-unusable source (for example a profile without resolvable
credentials) still falls back to the SDK
+ * default chain rather than failing.
+ * </p>
+ *
+ * @since 4.23
+ */
+public final class AwsRuntimeCredentialsResolver {
+
+ static final String ENV_ACCESS_KEY = "AWS_ACCESS_KEY_ID";
+ static final String ENV_SECRET_KEY = "AWS_SECRET_ACCESS_KEY";
+ static final String ENV_WEB_IDENTITY_TOKEN_FILE =
"AWS_WEB_IDENTITY_TOKEN_FILE";
+ static final String ENV_ROLE_ARN = "AWS_ROLE_ARN";
+ static final String ENV_CONTAINER_RELATIVE_URI =
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
+ static final String ENV_CONTAINER_FULL_URI =
"AWS_CONTAINER_CREDENTIALS_FULL_URI";
+ static final String ENV_PROFILE = "AWS_PROFILE";
+
+ static final String SYS_ACCESS_KEY = "aws.accessKeyId";
+ static final String SYS_SECRET_KEY = "aws.secretAccessKey";
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AwsRuntimeCredentialsResolver.class);
+
+ private AwsRuntimeCredentialsResolver() {
+ }
+
+ /**
+ * The credentials source detected for the current runtime.
+ */
+ public enum Source {
+ SYSTEM_PROPERTY("JVM system properties
(aws.accessKeyId/aws.secretAccessKey)"),
+ ENVIRONMENT("environment variables
(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)"),
+ WEB_IDENTITY("web identity token / IRSA
(AWS_WEB_IDENTITY_TOKEN_FILE)"),
+ PROFILE("shared profile (AWS_PROFILE or ~/.aws/credentials)"),
+ CONTAINER("container credentials (ECS task role / EKS Pod Identity)"),
+ UNKNOWN("no recognised runtime");
+
+ private final String description;
+
+ Source(String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+ }
+
+ /**
+ * Detect the current runtime and return the matching credentials
provider, or {@code null} when no runtime can be
+ * recognised (so the caller falls back to the SDK default credentials
provider chain).
+ *
+ * @return the resolved credentials provider, or {@code null} to use the
SDK default chain
+ */
+ public static AwsCredentialsProvider resolve() {
+ return resolve(RuntimeEnvironment.SYSTEM);
+ }
+
+ static AwsCredentialsProvider resolve(RuntimeEnvironment environment) {
+ Source source = detect(environment);
Review Comment:
**Design question (not a defect in the code as written):** the INFO line
names a *predicted* source, not the *observed* one.
`detect()` decides from env vars / file presence, and `resolve()` returns
`chain(providerFor(source), DefaultCredentialsProvider)`. When the predicted
head is unusable — the class Javadoc gives the example itself:
`~/.aws/credentials` exists but the default profile has no keys — the log says
`detected shared profile`, `ProfileCredentialsProvider` throws, the chain falls
through to the full default provider, and the credentials actually come from
the container or IMDS. So the one thing the option promises ("which source did
my credentials come from?") is wrong in precisely the situation a user would
enable it to diagnose. The same applies to `WEB_IDENTITY` when `sts` is absent.
The SDK already exposes the *observed* source, with no new option:
- `AwsCredentialsProviderChain` logs `Loading credentials from <provider>`
at DEBUG (checked in `auth-2.55.0.jar`, which `parent/pom.xml` pins now).
- Every resolved `AwsCredentials` carries `Identity.providerName()`
(`identity-spi-2.55.0`), naming the provider that actually produced it.
So an alternative that is never wrong and has nothing to keep in sync with
the SDK's chain order: keep the SDK default chain and log
`credentials.providerName()` at INFO after the first successful resolve (or
simply point users at the DEBUG logger). That's a bigger change of direction
than a review fix, so I'm raising it as a question for you and @davsclaus
rather than requesting changes. If the answer is "keep the option", it would at
least be worth wording the log line as "will try X first" so it doesn't claim
more than it knows.
##########
components/camel-aws/camel-aws-common/src/test/java/org/apache/camel/component/aws/common/AwsRuntimeCredentialsResolverTest.java:
##########
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.aws.common;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import
org.apache.camel.component.aws.common.AwsRuntimeCredentialsResolver.RuntimeEnvironment;
+import
org.apache.camel.component.aws.common.AwsRuntimeCredentialsResolver.Source;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AwsRuntimeCredentialsResolverTest {
+
+ @Test
+ void detectsSystemProperties() {
+ FakeEnvironment env = new FakeEnvironment()
+ .prop(AwsRuntimeCredentialsResolver.SYS_ACCESS_KEY, "AKIA")
+ .prop(AwsRuntimeCredentialsResolver.SYS_SECRET_KEY, "secret");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.SYSTEM_PROPERTY);
+ }
+
+ @Test
+ void detectsEnvironmentVariables() {
+ FakeEnvironment env = new FakeEnvironment()
+ .env(AwsRuntimeCredentialsResolver.ENV_ACCESS_KEY, "AKIA")
+ .env(AwsRuntimeCredentialsResolver.ENV_SECRET_KEY, "secret");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.ENVIRONMENT);
+ }
+
+ @Test
+ void detectsWebIdentity() {
+ FakeEnvironment env = new FakeEnvironment()
+
.env(AwsRuntimeCredentialsResolver.ENV_WEB_IDENTITY_TOKEN_FILE,
"/var/run/secrets/token")
+ .env(AwsRuntimeCredentialsResolver.ENV_ROLE_ARN,
"arn:aws:iam::123456789012:role/app");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.WEB_IDENTITY);
+ }
+
+ @Test
+ void detectsProfileFromEnv() {
+ FakeEnvironment env = new
FakeEnvironment().env(AwsRuntimeCredentialsResolver.ENV_PROFILE, "dev");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.PROFILE);
+ }
+
+ @Test
+ void detectsProfileFromCredentialsFile() {
+ FakeEnvironment env = new FakeEnvironment()
+ .home("/home/tester")
+ .file("/home/tester/.aws/credentials");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.PROFILE);
+ }
+
+ @Test
+ void bareConfigFileIsNotAProfileSignal() {
+ // ~/.aws/config often exists with only a region set - it must not be
read as "profile credentials detected".
+ FakeEnvironment env = new FakeEnvironment()
+ .home("/home/tester")
+ .file("/home/tester/.aws/config");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.UNKNOWN);
+ }
+
+ @Test
+ void detectsContainer() {
+ FakeEnvironment env = new FakeEnvironment()
+ .env(AwsRuntimeCredentialsResolver.ENV_CONTAINER_FULL_URI,
"http://169.254.170.23/v1/credentials");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.CONTAINER);
+ }
+
+ @Test
+ void unknownWhenNothingDetected() {
+ assertThat(AwsRuntimeCredentialsResolver.detect(new
FakeEnvironment())).isEqualTo(Source.UNKNOWN);
+ }
+
+ @Test
+ void mirrorsSdkOrderProfileBeforeContainer() {
+ // The SDK DefaultCredentialsProvider probes the profile provider
before the container provider - match it.
+ FakeEnvironment env = new FakeEnvironment()
+ .env(AwsRuntimeCredentialsResolver.ENV_PROFILE, "dev")
+ .env(AwsRuntimeCredentialsResolver.ENV_CONTAINER_FULL_URI,
"http://169.254.170.23/v1/credentials");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.PROFILE);
+ }
+
+ @Test
+ void environmentTakesPrecedenceOverProfile() {
+ FakeEnvironment env = new FakeEnvironment()
+ .env(AwsRuntimeCredentialsResolver.ENV_ACCESS_KEY, "AKIA")
+ .env(AwsRuntimeCredentialsResolver.ENV_SECRET_KEY, "secret")
+ .env(AwsRuntimeCredentialsResolver.ENV_PROFILE, "dev");
+
assertThat(AwsRuntimeCredentialsResolver.detect(env)).isEqualTo(Source.ENVIRONMENT);
+ }
+
+ @Test
+ void resolveReturnsChainForConcreteSource() {
+ FakeEnvironment env = new
FakeEnvironment().env(AwsRuntimeCredentialsResolver.ENV_PROFILE, "dev");
+ AwsCredentialsProvider provider =
AwsRuntimeCredentialsResolver.resolve(env);
+ assertThat(provider).isInstanceOf(AwsCredentialsProviderChain.class);
+ }
+
+ @Test
+ void resolveDelegatesToDefaultForWebIdentity() {
+ FakeEnvironment env = new FakeEnvironment()
+
.env(AwsRuntimeCredentialsResolver.ENV_WEB_IDENTITY_TOKEN_FILE,
"/var/run/secrets/token")
+ .env(AwsRuntimeCredentialsResolver.ENV_ROLE_ARN,
"arn:aws:iam::123456789012:role/app");
+ AwsCredentialsProvider provider =
AwsRuntimeCredentialsResolver.resolve(env);
+ assertThat(provider).isInstanceOf(DefaultCredentialsProvider.class);
+ }
+
+ @Test
+ void resolveReturnsNullWhenUnknown() {
Review Comment:
Minor: the resolver is well covered, but nothing exercises the
`AwsClientBuilderUtil` side — that `useAutoDetectCredentialsProvider=true`
actually takes precedence over `useDefaultCredentialsProvider` / static keys,
and that `warnOnConflictingCredentialsOptions` fires. A small test with a stub
`AwsCommonConfiguration` would close that gap.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]