From 92539783ba36df53fa54ca244004b5652414cf39 Mon Sep 17 00:00:00 2001 From: Keith Lustria Date: Fri, 7 Feb 2025 10:10:22 -0800 Subject: [PATCH] Use helidon.oci prefix for OCI Config and allow some oci auth types to accept federation-endpoint and tenancy-id (#9740) The PR fixes Issues 9681 and 9734 which includes the following: 1. Allow instance-principal, resource-principal and oke-workload-identity to accept federation-endpoint and tenancy-id as config parameters. This is originally targeted just for oke-workload-identity where Instance Metadata Service (IMDS) does not work on an OKE environment. Because of these, it is unable to assemble the target endpoint as it needs the IMDS to retrieve the region. To resolve the issue, the federation-endpoint configuration is now allowed to be explicitly specified to avoid generation of endpoint using the region from IMDS. In some examples of the use of oke-workload-identity, the tenancy id is required, so this configuration parameter is also added as an option. Furthermore, because instance-principal and resource-principal providers extends AbstractRequestingAuthenticationDetailsProvider similar to oke-workload-instance, hence they are included in the change to allow those optional parameters. 2. Fix a bug where the oci configuration does not work when prefixed with "helidon.oci". 3. Add comprehensive testing coverage for above changes. 4. Remove unnecessary Weight annotation with default value and replace WARNING message with TRACE when oci config does not use "helidon.oci" --- etc/copyright-exclude.txt | 1 + integrations/oci/authentication/README.md | 4 +- .../oci/authentication/instance/pom.xml | 10 ++ ...AuthenticationMethodInstancePrincipal.java | 10 +- .../InstancePrincipalBuilderProvider.java | 12 +- ...enticationMethodInstancePrincipalTest.java | 109 ++++++++++++++++++ ...AuthenticationMethodInstancePrincipal.java | 45 ++++++++ ...ockedInstancePrincipalBuilderProvider.java | 84 ++++++++++++++ .../oci/authentication/oke-workload/pom.xml | 17 +++ .../AuthenticationMethodOkeWorkload.java | 23 ++-- .../OkeWorkloadBuilderProvider.java | 6 +- .../AuthenticationMethodOkeWorkloadTest.java | 71 ++++++++++++ ...MockedAuthenticationMethodOkeWorkload.java | 42 +++++++ .../src/test/resources/dummy-ca.crt | 4 + .../oci/authentication/resource/pom.xml | 12 ++ ...AuthenticationMethodResourcePrincipal.java | 10 +- .../ResourcePrincipalBuilderProvider.java | 4 +- ...enticationMethodResourcePrincipalTest.java | 72 ++++++++++++ ...AuthenticationMethodResourcePrincipal.java | 41 +++++++ integrations/oci/oci/README.md | 15 +-- .../integrations/oci/OciConfigBlueprint.java | 16 ++- .../integrations/oci/OciConfigProvider.java | 15 ++- .../oci/OciConfigProviderTest.java | 51 ++++++++ 23 files changed, 634 insertions(+), 40 deletions(-) create mode 100644 integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/AuthenticationMethodInstancePrincipalTest.java create mode 100644 integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedAuthenticationMethodInstancePrincipal.java create mode 100644 integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedInstancePrincipalBuilderProvider.java create mode 100644 integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkloadTest.java create mode 100644 integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/MockedAuthenticationMethodOkeWorkload.java create mode 100644 integrations/oci/authentication/oke-workload/src/test/resources/dummy-ca.crt create mode 100644 integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipalTest.java create mode 100644 integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/MockedAuthenticationMethodResourcePrincipal.java create mode 100644 integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciConfigProviderTest.java diff --git a/etc/copyright-exclude.txt b/etc/copyright-exclude.txt index cf1dcc364f8..47723733b56 100644 --- a/etc/copyright-exclude.txt +++ b/etc/copyright-exclude.txt @@ -30,6 +30,7 @@ jaxb.index /MANIFEST.MF /README /CONTRIBUTING.md +.crt .pem .p8 .pkcs8.pem diff --git a/integrations/oci/authentication/README.md b/integrations/oci/authentication/README.md index a4bda918363..2097b71ff06 100644 --- a/integrations/oci/authentication/README.md +++ b/integrations/oci/authentication/README.md @@ -11,7 +11,7 @@ Helidon supports the following Authentication Methods: - `config-file`: based on OCI config file - `instance-principal`: instance principal (Such as an OCI VM) - `resource-prinicpal`: resource principal (Such as server-less functions) -- `workload`: workload running on a k8s cluster +- `oke-workload-identity`: identity of workload running on a k8s cluster The first two types are always available through `helidon-integrations-oci` module. -Instance, resource, and workload support can be added through modules in this project module. \ No newline at end of file +Instance, resource, and workload support can be added through modules in this project module. diff --git a/integrations/oci/authentication/instance/pom.xml b/integrations/oci/authentication/instance/pom.xml index fcff26ab150..031fe76a77b 100644 --- a/integrations/oci/authentication/instance/pom.xml +++ b/integrations/oci/authentication/instance/pom.xml @@ -63,6 +63,16 @@ slf4j-jdk14 test + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5 + test + + + org.mockito + mockito-core + test + diff --git a/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/AuthenticationMethodInstancePrincipal.java b/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/AuthenticationMethodInstancePrincipal.java index 0fadf2913b8..662b9b841f1 100644 --- a/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/AuthenticationMethodInstancePrincipal.java +++ b/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/AuthenticationMethodInstancePrincipal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package io.helidon.integrations.oci.authentication.instance; import java.util.Optional; +import java.util.function.Supplier; import io.helidon.common.LazyValue; import io.helidon.common.Weight; @@ -42,7 +43,7 @@ class AuthenticationMethodInstancePrincipal implements OciAuthenticationMethod { private final LazyValue> provider; AuthenticationMethodInstancePrincipal(OciConfig config, - InstancePrincipalsAuthenticationDetailsProviderBuilder builder) { + Supplier> builder) { provider = createProvider(config, builder); } @@ -58,10 +59,11 @@ public Optional provider() { private static LazyValue> createProvider(OciConfig config, - InstancePrincipalsAuthenticationDetailsProviderBuilder builder) { + Supplier> builder) { return LazyValue.create(() -> { if (HelidonOci.imdsAvailable(config)) { - return Optional.of(builder.build()); + return builder.get() + .map(InstancePrincipalsAuthenticationDetailsProviderBuilder::build); } if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { LOGGER.log(System.Logger.Level.TRACE, "OCI Metadata service is not available, " diff --git a/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/InstancePrincipalBuilderProvider.java b/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/InstancePrincipalBuilderProvider.java index 433c6d03a34..5698a238dfa 100644 --- a/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/InstancePrincipalBuilderProvider.java +++ b/integrations/oci/authentication/instance/src/main/java/io/helidon/integrations/oci/authentication/instance/InstancePrincipalBuilderProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,8 +39,8 @@ class InstancePrincipalBuilderProvider implements Supplier registry.get(BasicAuthenticationDetailsProvider.class)); + assertThat(thrown.getMessage(), + containsString(MockedInstancePrincipalBuilderProvider.INSTANCE_PRINCIPAL_INSTANTIATION_MESSAGE)); + + // The following validation indicates that the instance principal provider has been configured properly + assertThat(MockedAuthenticationMethodInstancePrincipal.getBuilder().getMetadataBaseUrl(), is(imdsBaseUri)); + assertThat(MockedAuthenticationMethodInstancePrincipal.getBuilder().getFederationEndpoint(), is(FEDERATION_ENDPOINT)); + assertThat(MockedAuthenticationMethodInstancePrincipal.getBuilder().getTenancyId(), is(TENANT_ID)); + } + + public static class ImdsEmulator { + // This will allow HelidonOci.imdsAvailable() to be tested by making the server that simulates IMDS to return a JSON value + // when instance property is queried + private static final String IMDS_INSTANCE_RESPONSE = """ + { + "displayName": "helidon-server" + } + """; + + private static void emulateImdsInstance(ServerRequest req, ServerResponse res) { + res.send(IMDS_INSTANCE_RESPONSE); + } + } +} diff --git a/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedAuthenticationMethodInstancePrincipal.java b/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedAuthenticationMethodInstancePrincipal.java new file mode 100644 index 00000000000..2e9e0e9610a --- /dev/null +++ b/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedAuthenticationMethodInstancePrincipal.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.instance; + +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.OciConfig; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider.InstancePrincipalsAuthenticationDetailsProviderBuilder; + +@Weight(Weighted.DEFAULT_WEIGHT) +@Service.Provider +class MockedAuthenticationMethodInstancePrincipal extends AuthenticationMethodInstancePrincipal { + + private static InstancePrincipalsAuthenticationDetailsProviderBuilder providerBuilder; + + MockedAuthenticationMethodInstancePrincipal(OciConfig config, + Supplier> + builder) { + super(config, builder); + providerBuilder = builder.get().get(); + } + + static InstancePrincipalsAuthenticationDetailsProviderBuilder getBuilder() { + return providerBuilder; + } +} diff --git a/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedInstancePrincipalBuilderProvider.java b/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedInstancePrincipalBuilderProvider.java new file mode 100644 index 00000000000..332a77d5bf7 --- /dev/null +++ b/integrations/oci/authentication/instance/src/test/java/io/helidon/integrations/oci/authentication/instance/MockedInstancePrincipalBuilderProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.instance; + +import java.util.function.Supplier; + +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.integrations.oci.OciConfig; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider.InstancePrincipalsAuthenticationDetailsProviderBuilder; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +@Weight(Weighted.DEFAULT_WEIGHT + 10) +@Service.Provider +class MockedInstancePrincipalBuilderProvider extends InstancePrincipalBuilderProvider + implements Supplier { + static final String INSTANCE_PRINCIPAL_INSTANTIATION_MESSAGE = "Instance Principal has been instantiated"; + private String metadataBaseUrl = null; + private String tenancyID = null; + private String federationEndpoint = null; + + MockedInstancePrincipalBuilderProvider(OciConfig config) { + super(config); + } + + @Override + InstancePrincipalsAuthenticationDetailsProviderBuilder getBuilder() { + // Mock the InstancePrincipalsAuthenticationDetailsProviderBuilder + final InstancePrincipalsAuthenticationDetailsProviderBuilder builder = + mock(InstancePrincipalsAuthenticationDetailsProviderBuilder.class); + + doAnswer(invocation -> { + throw new IllegalArgumentException(INSTANCE_PRINCIPAL_INSTANTIATION_MESSAGE); + }).when(builder).build(); + + // Process metadataBaseUrl + doAnswer(invocation -> { + metadataBaseUrl = invocation.getArgument(0); + return null; + }).when(builder).metadataBaseUrl(any()); + doAnswer(invocation -> { + return metadataBaseUrl; + }).when(builder).getMetadataBaseUrl(); + + // Process federationEndpoint + doAnswer(invocation -> { + federationEndpoint = invocation.getArgument(0); + return null; + }).when(builder).federationEndpoint(any()); + doAnswer(invocation -> { + return federationEndpoint; + }).when(builder).getFederationEndpoint(); + + // Process tenancyId + doAnswer(invocation -> { + tenancyID = invocation.getArgument(0); + return null; + }).when(builder).tenancyId(any()); + doAnswer(invocation -> { + return tenancyID; + }).when(builder).getTenancyId(); + + return builder; + } +} diff --git a/integrations/oci/authentication/oke-workload/pom.xml b/integrations/oci/authentication/oke-workload/pom.xml index 233fb9f03a2..c288de33ddb 100644 --- a/integrations/oci/authentication/oke-workload/pom.xml +++ b/integrations/oci/authentication/oke-workload/pom.xml @@ -67,6 +67,11 @@ slf4j-jdk14 test + + com.oracle.oci.sdk + oci-java-sdk-common-httpclient-jersey3 + test + @@ -111,6 +116,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + + ${project.build.testResources[0].directory}/dummy-ca.crt + + + diff --git a/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkload.java b/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkload.java index da25de69241..0910f0a5e46 100644 --- a/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkload.java +++ b/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,22 +21,22 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import java.util.function.Supplier; import io.helidon.common.LazyValue; import io.helidon.common.Weight; import io.helidon.common.Weighted; -import io.helidon.integrations.oci.OciConfig; import io.helidon.integrations.oci.spi.OciAuthenticationMethod; import io.helidon.service.registry.Service; import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider; -import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; +import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder; /** - * Instance principal authentication method, uses the - * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}. + * OKE workload identity authentication method, uses the + * {@link com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider}. */ -@Weight(Weighted.DEFAULT_WEIGHT - 40) +@Weight(Weighted.DEFAULT_WEIGHT - 50) @Service.Provider class AuthenticationMethodOkeWorkload implements OciAuthenticationMethod { private static final System.Logger LOGGER = System.getLogger(AuthenticationMethodOkeWorkload.class.getName()); @@ -53,8 +53,8 @@ class AuthenticationMethodOkeWorkload implements OciAuthenticationMethod { private final LazyValue> provider; - AuthenticationMethodOkeWorkload(OciConfig config) { - provider = createProvider(config); + AuthenticationMethodOkeWorkload(Supplier> builder) { + provider = createProvider(builder); } @Override @@ -67,11 +67,12 @@ public Optional provider() { return provider.get(); } - private static LazyValue> createProvider(OciConfig config) { + private static LazyValue> createProvider( + Supplier> builder) { return LazyValue.create(() -> { if (available()) { - return Optional.of(OkeWorkloadIdentityAuthenticationDetailsProvider.builder() - .build()); + return builder.get() + .map(OkeWorkloadIdentityAuthenticationDetailsProviderBuilder::build); } return Optional.empty(); diff --git a/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/OkeWorkloadBuilderProvider.java b/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/OkeWorkloadBuilderProvider.java index 3050e035e51..031238b826e 100644 --- a/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/OkeWorkloadBuilderProvider.java +++ b/integrations/oci/authentication/oke-workload/src/main/java/io/helidon/integrations/oci/authentication/okeworkload/OkeWorkloadBuilderProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder; /** - * OKE Workload builder provider, uses the + * OKE Workload Identity builder provider, uses the * {@link OkeWorkloadIdentityAuthenticationDetailsProviderBuilder}. */ @Service.Provider @@ -50,6 +50,8 @@ public OkeWorkloadIdentityAuthenticationDetailsProviderBuilder get() { config.imdsBaseUri() .map(URI::toString) .ifPresent(builder::metadataBaseUrl); + config.tenantId() + .ifPresent(builder::tenancyId); return builder; } diff --git a/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkloadTest.java b/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkloadTest.java new file mode 100644 index 00000000000..0cdbf5d77c3 --- /dev/null +++ b/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/AuthenticationMethodOkeWorkloadTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.okeworkload; + +import java.util.Properties; + +import io.helidon.service.registry.ServiceRegistry; +import io.helidon.service.registry.ServiceRegistryManager; + +import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AuthenticationMethodOkeWorkloadTest { + private static ServiceRegistryManager registryManager; + private static ServiceRegistry registry; + + void setUp(Properties p) { + p.put("helidon.oci.authentication-method", "oke-workload-identity"); + System.setProperties(p); + + registryManager = ServiceRegistryManager.create(); + registry = registryManager.registry(); + } + + @AfterEach + void cleanUp() { + registry = null; + if (registryManager != null) { + registryManager.shutdown(); + } + } + + @Test + public void testOkeWorkloadIdentityConfigurationAndInstantiation() { + final String FEDERATION_ENDPOINT = "https://auth.us-myregion-1.oraclecloud.com"; + final String TENANT_ID = "ocid1.tenancy.oc1..mytenancyid"; + + Properties p = System.getProperties(); + p.put("helidon.oci.federation-endpoint", FEDERATION_ENDPOINT); + p.put("helidon.oci.tenant-id", TENANT_ID); + setUp(p); + + // This error indicates that the oke-workload-identity provider has been instantiated + var thrown = assertThrows(IllegalArgumentException.class, + () -> registry.get(BasicAuthenticationDetailsProvider.class)); + assertThat(thrown.getMessage(), containsString("Invalid Kubernetes ca certification")); + // The following validation indicates that the oke-workload-identity provider has been configured properly + assertThat(MockedAuthenticationMethodOkeWorkload.getBuilder().getFederationEndpoint(), is(FEDERATION_ENDPOINT)); + assertThat(MockedAuthenticationMethodOkeWorkload.getBuilder().getTenancyId(), is(TENANT_ID)); + } +} diff --git a/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/MockedAuthenticationMethodOkeWorkload.java b/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/MockedAuthenticationMethodOkeWorkload.java new file mode 100644 index 00000000000..3c79f3c4fc4 --- /dev/null +++ b/integrations/oci/authentication/oke-workload/src/test/java/io/helidon/integrations/oci/authentication/okeworkload/MockedAuthenticationMethodOkeWorkload.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.okeworkload; + +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.okeworkloadidentity + .OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder; + +@Service.Provider +class MockedAuthenticationMethodOkeWorkload extends AuthenticationMethodOkeWorkload { + + private static OkeWorkloadIdentityAuthenticationDetailsProviderBuilder providerBuilder; + + MockedAuthenticationMethodOkeWorkload(Supplier> builder) { + super(builder); + providerBuilder = builder.get().get(); + } + + static OkeWorkloadIdentityAuthenticationDetailsProviderBuilder getBuilder() { + return providerBuilder; + } +} diff --git a/integrations/oci/authentication/oke-workload/src/test/resources/dummy-ca.crt b/integrations/oci/authentication/oke-workload/src/test/resources/dummy-ca.crt new file mode 100644 index 00000000000..653150c6a22 --- /dev/null +++ b/integrations/oci/authentication/oke-workload/src/test/resources/dummy-ca.crt @@ -0,0 +1,4 @@ +NOTICE: +======= +This file represents a dummy ca.crt that will be used by the AuthenticationMethodOkeWorkload to validate if it can proceed with +authentication processing. diff --git a/integrations/oci/authentication/resource/pom.xml b/integrations/oci/authentication/resource/pom.xml index 89b90c668da..74d32e8f585 100644 --- a/integrations/oci/authentication/resource/pom.xml +++ b/integrations/oci/authentication/resource/pom.xml @@ -107,6 +107,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + + 2.2 + + + diff --git a/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipal.java b/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipal.java index 32d356c9779..2cdc21cbbc1 100644 --- a/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipal.java +++ b/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.lang.System.Logger.Level; import java.util.Optional; +import java.util.function.Supplier; import io.helidon.common.LazyValue; import io.helidon.common.Weight; @@ -42,7 +43,7 @@ class AuthenticationMethodResourcePrincipal implements OciAuthenticationMethod { private final LazyValue> provider; - AuthenticationMethodResourcePrincipal(ResourcePrincipalAuthenticationDetailsProviderBuilder builder) { + AuthenticationMethodResourcePrincipal(Supplier> builder) { provider = createProvider(builder); } @@ -57,7 +58,7 @@ public Optional provider() { } private static LazyValue> - createProvider(ResourcePrincipalAuthenticationDetailsProviderBuilder builder) { + createProvider(Supplier> builder) { return LazyValue.create(() -> { // https://github.com/oracle/oci-java-sdk/blob/v2.19.0/bmc-common/src/main/java/com/oracle/bmc/auth/ResourcePrincipalAuthenticationDetailsProvider.java#L246-L251 @@ -68,7 +69,8 @@ public Optional provider() { } return Optional.empty(); } - return Optional.of(builder.build()); + return builder.get() + .map(ResourcePrincipalAuthenticationDetailsProviderBuilder::build); }); } } diff --git a/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/ResourcePrincipalBuilderProvider.java b/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/ResourcePrincipalBuilderProvider.java index 9c3de7bf512..b4471d9aeb9 100644 --- a/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/ResourcePrincipalBuilderProvider.java +++ b/integrations/oci/authentication/resource/src/main/java/io/helidon/integrations/oci/authentication/resource/ResourcePrincipalBuilderProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,8 @@ public ResourcePrincipalAuthenticationDetailsProviderBuilder get() { config.imdsBaseUri() .map(URI::toString) .ifPresent(builder::metadataBaseUrl); + config.tenantId() + .ifPresent(builder::tenancyId); return builder; } diff --git a/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipalTest.java b/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipalTest.java new file mode 100644 index 00000000000..166ad470f67 --- /dev/null +++ b/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/AuthenticationMethodResourcePrincipalTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.resource; + +import java.util.Properties; + +import io.helidon.service.registry.ServiceRegistry; +import io.helidon.service.registry.ServiceRegistryManager; + +import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AuthenticationMethodResourcePrincipalTest { + private static ServiceRegistryManager registryManager; + private static ServiceRegistry registry; + + void setUp(Properties p) { + p.put("helidon.oci.authentication-method", "resource-principal"); + System.setProperties(p); + + registryManager = ServiceRegistryManager.create(); + registry = registryManager.registry(); + } + + @AfterEach + void cleanUp() { + registry = null; + if (registryManager != null) { + registryManager.shutdown(); + } + } + + @Test + public void testResourcePrincipalConfigurationAndInstantiation() { + final String FEDERATION_ENDPOINT = "https://auth.us-myregion-1.oraclecloud.com"; + final String TENANT_ID = "ocid1.tenancy.oc1..mytenancyid"; + + Properties p = System.getProperties(); + p.put("helidon.oci.federation-endpoint", FEDERATION_ENDPOINT); + p.put("helidon.oci.tenant-id", TENANT_ID); + setUp(p); + + // This error indicates that the resource principal provider has been instantiated + var thrown = assertThrows(IllegalArgumentException.class, + () -> registry.get(BasicAuthenticationDetailsProvider.class)); + assertThat(thrown.getMessage(), + containsString("Resource principals authentication can only be used in certain OCI services")); + // The following validation indicates that the resource principal provider has been configured properly + assertThat(MockedAuthenticationMethodResourcePrincipal.getBuilder().getFederationEndpoint(), is(FEDERATION_ENDPOINT)); + assertThat(MockedAuthenticationMethodResourcePrincipal.getBuilder().getTenancyId(), is(TENANT_ID)); + } +} diff --git a/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/MockedAuthenticationMethodResourcePrincipal.java b/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/MockedAuthenticationMethodResourcePrincipal.java new file mode 100644 index 00000000000..bcc64a46abb --- /dev/null +++ b/integrations/oci/authentication/resource/src/test/java/io/helidon/integrations/oci/authentication/resource/MockedAuthenticationMethodResourcePrincipal.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci.authentication.resource; + +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.Weight; +import io.helidon.common.Weighted; +import io.helidon.service.registry.Service; + +import com.oracle.bmc.auth.ResourcePrincipalAuthenticationDetailsProvider.ResourcePrincipalAuthenticationDetailsProviderBuilder; + +@Service.Provider +class MockedAuthenticationMethodResourcePrincipal extends AuthenticationMethodResourcePrincipal { + + private static ResourcePrincipalAuthenticationDetailsProviderBuilder providerBuilder; + + MockedAuthenticationMethodResourcePrincipal(Supplier> builder) { + super(builder); + providerBuilder = builder.get().get(); + } + + static ResourcePrincipalAuthenticationDetailsProviderBuilder getBuilder() { + return providerBuilder; + } +} diff --git a/integrations/oci/oci/README.md b/integrations/oci/oci/README.md index 63c741b50e1..3239cec81ea 100644 --- a/integrations/oci/oci/README.md +++ b/integrations/oci/oci/README.md @@ -38,13 +38,14 @@ The provider is looked up by using instances of `io.helidon.integrations.oci.spi The following out-of-the-box implementations exist: -| Provider | Weight | Description | -|--------------------|--------|---------------------------------------------------------------| -| Config | 90 | Uses `OciConfig` | -| Session Token | 85 | Uses `OciConfig` or config file, if it contains session token | -| Config File | 80 | Uses `~/.oci/config` file | -| Resource Principal | 70 | Resource principal (such as functions) | -| Instance Principal | 60 | Principal of the compute instance | +| Provider | Weight | Description | +|-----------------------|--------|---------------------------------------------------------------| +| Config | 90 | Uses `OciConfig` | +| Session Token | 85 | Uses `OciConfig` or config file, if it contains session token | +| Config File | 80 | Uses `~/.oci/config` file | +| Resource Principal | 70 | Resource principal (such as functions) | +| Instance Principal | 60 | Principal of the compute instance | +| OKE Workload Identity | 50 | Identity of the OKE Workload | To create a custom instance of authentication details provider, just create a new service for service registry with default or higher weight that provides an instance of the `BasicAuthenticationDetailsProvider` diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java index c736ecf8d10..ee52dc444da 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigBlueprint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,8 +72,9 @@ interface OciConfigBlueprint { *
  • {@code instance-principal} - use identity of the OCI instance the service is running on, uses * {@link com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider}, and is available in a * separate module {@code helidon-integrations-oci-authentication-resource}
  • - *
  • {@code workload} - use workload identity of the OCI Kubernetes workload, available in a - * separate module {@code helidon-integrations-oci-authentication-workload}
  • + *
  • {@code oke-workload-identity} - use identity of the OCI Kubernetes workload, uses + * {@code com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider}, and is available in a + * separate module {@code helidon-integrations-oci-authentication-oke-workload}
  • * * * @return the authentication method to apply @@ -161,8 +162,17 @@ interface OciConfigBlueprint { * * @return custom federation endpoint URI */ + @Option.Configured Optional federationEndpoint(); + /** + * OCI tenant id for Instance Principal, Resource Principal or OKE Workload. + * + * @return the OCI tenant id + */ + @Option.Configured + Optional tenantId(); + /** * Get the config used to update the builder. * diff --git a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java index 5d0771bc4d9..9adb5d306b3 100644 --- a/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java +++ b/integrations/oci/oci/src/main/java/io/helidon/integrations/oci/OciConfigProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ @Weight(Weighted.DEFAULT_WEIGHT - 10) class OciConfigProvider implements Supplier { private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); + private static final String CONFIG_PREFIX = "helidon.oci"; + private static final System.Logger LOGGER = System.getLogger(AuthenticationMethodConfig.class.getName()); private static volatile OciConfig ociConfig; OciConfigProvider() { @@ -71,7 +73,14 @@ private static void create() { ConfigSources.systemProperties(), ConfigSources.file("oci-config.yaml").optional(true), ConfigSources.classpath("oci-config.yaml").optional(true)); - - ociConfig = OciConfig.create(config); + if (config.get(CONFIG_PREFIX).exists()) { + ociConfig = OciConfig.create(config.get(CONFIG_PREFIX)); + } else { + ociConfig = OciConfig.create(config); + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { + LOGGER.log(System.Logger.Level.TRACE, + String.format("OCI Configuration needs to be prefixed with \"%s\"", CONFIG_PREFIX)); + } + } } } diff --git a/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciConfigProviderTest.java b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciConfigProviderTest.java new file mode 100644 index 00000000000..312acddfbbc --- /dev/null +++ b/integrations/oci/oci/src/test/java/io/helidon/integrations/oci/OciConfigProviderTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * + * 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 + * + * 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 io.helidon.integrations.oci; + +import java.util.Properties; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class OciConfigProviderTest { + @Test + void testConfig() { + final String FEDERATION_ENDPOINT = "https://auth.us-myregion-1.oraclecloud.com"; + final String TENANT_ID = "ocid1.tenancy.oc1..mytenancyid"; + final String REGION = "us-phoenix-1"; + final String CONFIG_FILE_PATH = "/path1/path2/.oci/config"; + + Properties p = System.getProperties(); + p.put("helidon.oci.authentication-method", AuthenticationMethodConfigFile.METHOD); + p.put("helidon.oci.federation-endpoint", FEDERATION_ENDPOINT); + p.put("helidon.oci.tenant-id", TENANT_ID); + p.put("HELIDON_OCI_REGION", REGION); + p.put("helidon.oci.authentication.config-file.path", CONFIG_FILE_PATH); + System.setProperties(p); + + // clean up ociConfig from OciConfigProvider + OciConfigProvider.config(null); + var ociConfig = new OciConfigProvider().get(); + assertThat(ociConfig.authenticationMethod(), is(AuthenticationMethodConfigFile.METHOD)); + assertThat(ociConfig.federationEndpoint().get().toString(), is(FEDERATION_ENDPOINT)); + assertThat(ociConfig.tenantId().get().toString(), is(TENANT_ID)); + assertThat(ociConfig.region().get().getRegionId(), is(REGION)); + assertThat(ociConfig.configFileMethodConfig().get().path().get(), is(CONFIG_FILE_PATH)); + } +}