diff --git a/ojdbc-provider-aws/README.md b/ojdbc-provider-aws/README.md index 761ebcc9..f78e27bf 100644 --- a/ojdbc-provider-aws/README.md +++ b/ojdbc-provider-aws/README.md @@ -12,6 +12,8 @@ Provider
AWS Secrets Manager Configuration Provider
Provides connection properties managed by the Secrets Manager service
+
AWS AppConfig Freeform Configuration Provider
+
Provides connection properties managed by the AWS AppConfig Freeform Configuration service
Common Parameters for Centralized Config Providers
Common parameters supported by the config providers
Caching configuration
@@ -152,6 +154,28 @@ jdbc:oracle:thin:@config-awssecretsmanager://{secret-name} The JSON Payload retrieved by AWS Secrets Manager Provider follows the same format in [AWS S3 Configuration Provider](#json-payload-format). +## AWS AppConfig Freeform Config Provider +The Oracle DataSource uses the prefix `jdbc:oracle:thin:@config-awsappconfig` to identify that the freeform +configuration parameters should be loaded using AWS AppConfig. Users need to specify the application identifier or name, along with the environment and configuration profile + +A URL with the following format is valid: + +
+jdbc:oracle:thin:@config-awsappconfig://{application-identifier}
+
+ + +The `{application-identifier}` can be either the application ID or name as defined in [AWS AppConfig](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/appconfigdata/AppConfigDataClient.html). +The environment and configuration profile can be specified in the URL, + +for example: +
+jdbc:oracle:thin:@config-awsappconfig://app-name?appconfig_environment=your-environment&appconfig_profile=your-profile
+
+ +Alternatively, you can set them via system properties (`aws.appconfig.environment, aws.appconfig.profile`) or +environment variables (`AWS_APP_CONFIG_ENVIRONMENT, AWS_APP_CONFIG_PROFILE`). + ## Common Parameters for Centralized Config Providers AWS S3 Configuration Provider and AWS Secrets Manager Configuration Provider share the same sets of parameters for authentication configuration. diff --git a/ojdbc-provider-aws/pom.xml b/ojdbc-provider-aws/pom.xml index 381b374a..784ad70d 100644 --- a/ojdbc-provider-aws/pom.xml +++ b/ojdbc-provider-aws/pom.xml @@ -66,6 +66,10 @@ software.amazon.awssdk secretsmanager + + software.amazon.awssdk + appconfigdata + com.oracle.database.jdbc diff --git a/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/appconfig/AppConfigFactory.java b/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/appconfig/AppConfigFactory.java new file mode 100644 index 00000000..c3b2343e --- /dev/null +++ b/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/appconfig/AppConfigFactory.java @@ -0,0 +1,160 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + +package oracle.jdbc.provider.aws.appconfig; + +import oracle.jdbc.provider.aws.AwsResourceFactory; +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.parameter.Parameter; +import oracle.jdbc.provider.parameter.ParameterSet; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; +import software.amazon.awssdk.services.appconfigdata.model.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.*; + +/** + * A factory for retrieving Freeform Configurations data from AWS + * AppConfig using the AppConfig Data API. This factory establishes a + * configuration session and fetches the latest configuration data based on + * the provided application, environment, and configuration profile identifiers. + */ +public class AppConfigFactory extends AwsResourceFactory { + + /** + * Parameter for the AWS AppConfig application identifier. + * This is a required parameter and can be either the application ID or the + * application name (e.g., "my-app") as defined in AWS AppConfig. + */ + public static final Parameter APP_CONFIG_APPLICATION = Parameter.create(); + + /** + * Parameter for the AWS AppConfig environment identifier. + * This is a required parameter and can be either the environment ID or the + * environment name as defined in AWS AppConfig. + */ + public static final Parameter APP_CONFIG_ENVIRONMENT = Parameter.create(); + + /** + * Parameter for the AWS AppConfig configuration profile identifier. + * This is a required parameter and can be either the configuration profile ID + * or the configuration profile name as defined in AWS AppConfig. + */ + public static final Parameter APP_CONFIG_PROFILE = Parameter.create(); + + // System property and environment variable keys for fallback values + private static final String SYS_PROP_ENVIRONMENT = "aws.appconfig.environment"; + private static final String ENV_VAR_ENVIRONMENT = "AWS_APP_CONFIG_ENVIRONMENT"; + private static final String SYS_PROP_PROFILE = "aws.appconfig.profile"; + private static final String ENV_VAR_PROFILE = "AWS_APP_CONFIG_PROFILE"; + + + private static final ResourceFactory INSTANCE = new AppConfigFactory(); + + private AppConfigFactory() {} + + /** + * @return a singleton of {@code AppConfigFactory} + */ + public static ResourceFactory getInstance() { + return INSTANCE; + } + + @Override + public Resource request(AwsCredentials awsCredentials, ParameterSet parameterSet) { + String applicationId = parameterSet.getRequired(APP_CONFIG_APPLICATION); + String environmentId = getParameterWithFallback(APP_CONFIG_ENVIRONMENT, SYS_PROP_ENVIRONMENT, ENV_VAR_ENVIRONMENT, parameterSet); + String configurationProfileId = getParameterWithFallback(APP_CONFIG_PROFILE, SYS_PROP_PROFILE, ENV_VAR_PROFILE, parameterSet); + String region = parameterSet.getOptional(REGION); + + try (AppConfigDataClient client = AppConfigDataClient.builder() + .credentialsProvider(() -> awsCredentials) + .applyMutation(builder -> { + if (region != null) builder.region(Region.of(region)); + }) + .build()) { + + final StartConfigurationSessionResponse sessionResponse = client.startConfigurationSession( + StartConfigurationSessionRequest.builder() + .applicationIdentifier(applicationId) + .environmentIdentifier(environmentId) + .configurationProfileIdentifier(configurationProfileId) + .build()); + + final String token = sessionResponse.initialConfigurationToken(); + + final GetLatestConfigurationResponse configResponse = client.getLatestConfiguration( + GetLatestConfigurationRequest.builder() + .configurationToken(token) + .build()); + return Resource.createPermanentResource( + new ByteArrayInputStream(configResponse.configuration().asByteArray()), + false); + } + } + + /** + * Retrieves a parameter value with fallback to system property and environment variable. + * @param parameter The Parameter object to retrieve from ParameterSet. + * @param sysPropKey The system property key to check. + * @param envVarKey The environment variable key to check. + * @param paramSet The ParameterSet to check first. + * @return The parameter value, or throws an exception if not found. + * @throws IllegalArgumentException if the parameter is not found in any source. + */ + private static String getParameterWithFallback(Parameter parameter, String sysPropKey, String envVarKey, ParameterSet paramSet) { + String value = paramSet.getOptional(parameter); + if (value == null) { + value = System.getProperty(sysPropKey); + if (value == null) { + value = System.getenv(envVarKey); + if (value == null) { + throw new IllegalArgumentException( + String.format("Parameter '%s' is required and not found in ParameterSet, system property '%s', or environment variable '%s'", + paramSet.getName(parameter), sysPropKey, envVarKey)); + } + } + } + return value; + } +} \ No newline at end of file diff --git a/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigConfigurationProvider.java b/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigConfigurationProvider.java new file mode 100644 index 00000000..7ef9e2ab --- /dev/null +++ b/ojdbc-provider-aws/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigConfigurationProvider.java @@ -0,0 +1,116 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + +package oracle.jdbc.provider.aws.configuration; + +import oracle.jdbc.driver.configuration.OracleConfigurationParsableProvider; +import oracle.jdbc.provider.aws.appconfig.AppConfigFactory; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.jdbc.provider.parameter.ParameterSetParser; +import oracle.jdbc.util.OracleConfigurationCache; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import static oracle.jdbc.provider.aws.appconfig.AppConfigFactory.*; +import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.KEY; +import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.REGION; + +/** + * A provider for JSON payload which contains configuration data from AWS + * AppConfig + * See {@link #getInputStream(String)} for the spec of the JSON payload. + **/ +public class AwsAppConfigConfigurationProvider extends OracleConfigurationParsableProvider { + + static final ParameterSetParser PARAMETER_SET_PARSER = AwsConfigurationParameters.configureBuilder( + ParameterSetParser.builder() + .addParameter("value", APP_CONFIG_APPLICATION) + .addParameter("appconfig_environment", APP_CONFIG_ENVIRONMENT) + .addParameter("appconfig_profile", APP_CONFIG_PROFILE) + .addParameter("key", KEY) + .addParameter("AWS_REGION", REGION)) + .build(); + + /** + * {@inheritDoc} + *

+ * Returns the JSON payload stored in AWS AppConfig. + *

+ * + * @param parameterName The application identifier or name for the + * configuration + * @return JSON payload as an InputStream + */ + @Override + public InputStream getInputStream(String parameterName) { + final String VALUE = "value"; + Map opts = new HashMap<>(options); + opts.put(VALUE, parameterName); + + ParameterSet parameters = PARAMETER_SET_PARSER.parseNamedValues(opts); + + return AppConfigFactory.getInstance() + .request(parameters) + .getContent(); + } + + @Override + public String getType() { + return "awsappconfig"; + } + + /** + * {@inheritDoc} + * @return cache of this provider which is used to store configuration + */ + @Override + public OracleConfigurationCache getCache() { + return CACHE; + } + + /** + * {@inheritDoc} + * @return the parser type + */ + @Override + public String getParserType(String location) { + return "json"; + } +} \ No newline at end of file diff --git a/ojdbc-provider-aws/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider b/ojdbc-provider-aws/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider index d09ec836..1fa6486e 100644 --- a/ojdbc-provider-aws/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider +++ b/ojdbc-provider-aws/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider @@ -1,2 +1,3 @@ oracle.jdbc.provider.aws.configuration.AwsS3ConfigurationProvider -oracle.jdbc.provider.aws.configuration.AwsSecretsManagerConfigurationProvider \ No newline at end of file +oracle.jdbc.provider.aws.configuration.AwsSecretsManagerConfigurationProvider +oracle.jdbc.provider.aws.configuration.AwsAppConfigConfigurationProvider \ No newline at end of file diff --git a/ojdbc-provider-aws/src/test/java/oracle/provider/aws/AwsTestProperty.java b/ojdbc-provider-aws/src/test/java/oracle/provider/aws/AwsTestProperty.java index 62d5f578..e3f54ad7 100644 --- a/ojdbc-provider-aws/src/test/java/oracle/provider/aws/AwsTestProperty.java +++ b/ojdbc-provider-aws/src/test/java/oracle/provider/aws/AwsTestProperty.java @@ -40,6 +40,7 @@ public enum AwsTestProperty { AWS_S3_URL, AWS_SECRETS_MANAGER_URL, + AWS_APP_CONFIG_URL, AWS_REGION, DB_CREDENTIALS_SECRET_NAME, TNSNAMES_SECRET_NAME, diff --git a/ojdbc-provider-aws/src/test/java/oracle/provider/aws/configuration/AwsAppConfigFreeformConfigurationProviderTest.java b/ojdbc-provider-aws/src/test/java/oracle/provider/aws/configuration/AwsAppConfigFreeformConfigurationProviderTest.java new file mode 100644 index 00000000..906ae83c --- /dev/null +++ b/ojdbc-provider-aws/src/test/java/oracle/provider/aws/configuration/AwsAppConfigFreeformConfigurationProviderTest.java @@ -0,0 +1,81 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + +package oracle.provider.aws.configuration; + +import oracle.jdbc.provider.TestProperties; +import oracle.jdbc.spi.OracleConfigurationProvider; +import oracle.provider.aws.AwsTestProperty; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AwsAppConfigFreeformConfigurationProviderTest { + + static { + OracleConfigurationProvider.allowedProviders.add("awsappconfig"); + } + + private static final OracleConfigurationProvider PROVIDER = + OracleConfigurationProvider.find("awsappconfig"); + + /** + * Verifies if AWS AppConfig Freeform Configuration Provider works with default authentication + * @throws SQLException + */ + @Test + public void testDefaultAuthentication() throws SQLException { + final String prefix = "jdbc:oracle:thin:@config-awsappconfig://"; + + String url = TestProperties + .getOrAbort(AwsTestProperty.AWS_APP_CONFIG_URL); + + assertTrue(url.startsWith(prefix), + "AWS_APP_CONFIG_URL should start with " + prefix); + + Properties properties = PROVIDER + .getConnectionProperties(url.substring(prefix.length())); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } +} \ No newline at end of file diff --git a/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigFreeformConfigurationExample.java b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigFreeformConfigurationExample.java new file mode 100644 index 00000000..1a2b2732 --- /dev/null +++ b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/aws/configuration/AwsAppConfigFreeformConfigurationExample.java @@ -0,0 +1,89 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ + +package oracle.jdbc.provider.aws.configuration; + +import oracle.jdbc.datasource.impl.OracleDataSource; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * A standalone example that configures Oracle JDBC to be provided with the + * connection properties retrieved from AWS AppConfig Freeform Configuration. + */ +public class AwsAppConfigFreeformConfigurationExample { + private static String url; + + /** + *

+ * A simple example to retrieve connection properties from AWS AppConfig Freeform Configuration. + *

+ * For the default authentication, the only required local configuration is + * to have a valid AWS Config in ~/.aws/config and ~/.aws/credentials. + *

+ * @param args the command line arguments + * @throws SQLException if an error occurs during the database calls + */ + public static void main(String[] args) throws SQLException { + + // Sample default URL if non present + if (args.length == 0) { + // A URL with the following format is valid: + // "jdbc:oracle:thin:@config-awsappconfig://{application-identifier}" + // The {application-identifier} can be the application ID or name, and + url = "jdbc:oracle:thin:@config-awsappconfig://app-name" + + "?appconfig_environment=your-environment&appconfig_profile=your-profile"; + } else { + url = args[0]; + } + + // No changes required, configuration provider is loaded at runtime + OracleDataSource ds = new OracleDataSource(); + ds.setURL(url); + + // Standard JDBC code + Connection cn = ds.getConnection(); + Statement st = cn.createStatement(); + ResultSet rs = st.executeQuery("SELECT 'Hello, db' FROM sys.dual"); + if (rs.next()) + System.out.println(rs.getString(1)); + } +} \ No newline at end of file